var http = require('http');
var childProcess = require('child_process');
var meshCoreObj = { action: 'coreinfo', value: "MeshCore Recovery", caps: 14 }; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript
var nextTunnelIndex = 1;
var tunnels = {};
var fs = require('fs');

if (require('MeshAgent').ARCHID == null) {
    var id = null;
    switch (process.platform) {
        case 'win32':
            id = require('_GenericMarshal').PointerSize == 4 ? 3 : 4;
            break;
        case 'freebsd':
            id = require('_GenericMarshal').PointerSize == 4 ? 31 : 30;
            break;
        case 'darwin':
            try {
                id = require('os').arch() == 'x64' ? 16 : 29;
            }
            catch (xx) {
                id = 16;
            }
            break;
    }
    if (id != null) { Object.defineProperty(require('MeshAgent'), 'ARCHID', { value: id }); }
}

//attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); });

function sendConsoleText(msg, sessionid) {
    if (sessionid != null) {
        require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg, sessionid: sessionid });
    }
    else {
        require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: msg });
    }
}

function sendAgentMessage(msg, icon) {
    if (sendAgentMessage.messages == null) {
        sendAgentMessage.messages = {};
        sendAgentMessage.nextid = 1;
    }
    sendAgentMessage.messages[sendAgentMessage.nextid++] = { msg: msg, icon: icon };
    require('MeshAgent').SendCommand({ action: 'sessions', type: 'msg', value: sendAgentMessage.messages });
}

// Add to the server event log
function MeshServerLog(msg, state) {
    if (typeof msg == 'string') { msg = { action: 'log', msg: msg }; } else { msg.action = 'log'; }
    if (state) {
        if (state.userid) { msg.userid = state.userid; }
        if (state.username) { msg.username = state.username; }
        if (state.sessionid) { msg.sessionid = state.sessionid; }
        if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
    }
    require('MeshAgent').SendCommand(msg);
}

// Add to the server event log, use internationalized events
function MeshServerLogEx(id, args, msg, state) {
    var msg = { action: 'log', msgid: id, msgArgs: args, msg: msg };
    if (state) {
        if (state.userid) { msg.userid = state.userid; }
        if (state.username) { msg.username = state.username; }
        if (state.sessionid) { msg.sessionid = state.sessionid; }
        if (state.remoteaddr) { msg.remoteaddr = state.remoteaddr; }
    }
    require('MeshAgent').SendCommand(msg);
}

function getOpenDescriptors()
{
    switch(process.platform)
    {
        case "freebsd":
            var child = require('child_process').execFile('/bin/sh', ['sh']);
            child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
            child.stderr.on('data', function (c) { });

            child.stdin.write("procstat -f " + process.pid + " | tr '\\n' '`' | awk -F'`' '");
            child.stdin.write('{');
            child.stdin.write('   DEL="";');
            child.stdin.write('   printf "[";');
            child.stdin.write('   for(i=1;i<NF;++i)');
            child.stdin.write('   {');
            child.stdin.write('      A=split($i,B," ");');
            child.stdin.write('      if(B[3] ~ /^[0-9]/)');
            child.stdin.write('      {');
            child.stdin.write('         printf "%s%s", DEL, B[3];');
            child.stdin.write('         DEL=",";');
            child.stdin.write('      }');
            child.stdin.write('   }');
            child.stdin.write('   printf "]";');
            child.stdin.write("}'");

            child.stdin.write('\nexit\n');
            child.waitExit();

            try
            {
                return(JSON.parse(child.stdout.str.trim()));
            }
            catch(e)
            {
                return ([]);
            }
            break;
        case "linux":
            var child = require('child_process').execFile('/bin/sh', ['sh']);
            child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
            child.stderr.on('data', function (c) { });

            child.stdin.write("ls /proc/" + process.pid + "/fd | tr '\\n' '`' | awk -F'`' '");
            child.stdin.write('{');
            child.stdin.write('   printf "[";');
            child.stdin.write('   DEL="";');
            child.stdin.write('   for(i=1;i<NF;++i)');
            child.stdin.write('   {');
            child.stdin.write('      printf "%s%s",DEL,$i;');
            child.stdin.write('      DEL=",";');
            child.stdin.write('   }');
            child.stdin.write('   printf "]";');
            child.stdin.write("}'");
            child.stdin.write('\nexit\n');
            child.waitExit();

            try
            {
                return (JSON.parse(child.stdout.str.trim()));
            }
            catch (e)
            {
                return ([]);
            }
            break;
        default:
            return ([]);
    }
}


function pathjoin() {
    var x = [];
    for (var i in arguments) {
        var w = arguments[i];
        if (w != null) {
            while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
            if (i != 0) {
                while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); }
            }
            x.push(w);
        }
    }
    if (x.length == 0) return '/';
    return x.join('/');
}
// Replace a string with a number if the string is an exact number
function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) === x)) { x = parseInt(x); } return x; }


function closeDescriptors(libc, descriptors)
{
    var fd = null;
    while(descriptors.length>0)
    {
        fd = descriptors.pop();
        if(fd > 2)
        {
            libc.close(fd);
        }
    }
}

function linux_execv(name, agentfilename, sessionid) {
    var libs = require('monitor-info').getLibInfo('libc');
    var libc = null;

    while (libs.length > 0) {
        try {
            libc = require('_GenericMarshal').CreateNativeProxy(libs.pop().path);
            break;
        }
        catch (e) {
            libc = null;
            continue;
        }
    }
    if (libc != null) {
        try
        {
            libc.CreateMethod('execv');
            libc.CreateMethod('close');
        }
        catch (e) {
            libc = null;
        }
    }

    if (libc == null) {
        // Couldn't find libc.so, fallback to using service manager to restart agent
        if (sessionid != null) { sendConsoleText('Restarting service via service-manager...', sessionid) }
        try {
            // restart service
            var s = require('service-manager').manager.getService(name);
            s.restart();
        }
        catch (zz) {
            sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
            sendAgentMessage('Self Update encountered an error trying to restart service', 3);
        }
        return;
    }

    if (sessionid != null) { sendConsoleText('Restarting service via execv()...', sessionid) }

    var i;
    var args;
    var argarr = [process.execPath];
    var path = require('_GenericMarshal').CreateVariable(process.execPath);

    if (require('MeshAgent').getStartupOptions != null) {
        var options = require('MeshAgent').getStartupOptions();
        for (i in options) {
            argarr.push('--' + i + '="' + options[i] + '"');
        }
    }

    args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
    for (i = 0; i < argarr.length; ++i) {
        var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
        arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
    }

    var descriptors = getOpenDescriptors();
    closeDescriptors(libc, descriptors);

    libc.execv(path, args);
    if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
    sendAgentMessage('Self Update failed because execv() failed', 3);
}

function bsd_execv(name, agentfilename, sessionid) {
    var child = require('child_process').execFile('/bin/sh', ['sh']);
    child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
    child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
    child.stdin.write("cat /usr/lib/libc.so | awk '");
    child.stdin.write('{');
    child.stdin.write(' a=split($0, tok, "(");');
    child.stdin.write(' if(a>1)');
    child.stdin.write(' {');
    child.stdin.write('     split(tok[2], b, ")");');
    child.stdin.write('     split(b[1], c, " ");');
    child.stdin.write('     print c[1];');
    child.stdin.write(' }');
    child.stdin.write("}'\nexit\n");
    child.waitExit();
    if (child.stdout.str.trim() == '') {
        if (sessionid != null) { sendConsoleText('Self Update failed because cannot find libc.so', sessionid) }
        sendAgentMessage('Self Update failed because cannot find libc.so', 3);
        return;
    }

    var libc = null;
    try
    {
        libc = require('_GenericMarshal').CreateNativeProxy(child.stdout.str.trim());
        libc.CreateMethod('execv');
        libc.CreateMethod('close');
    }
    catch (e) {
        if (sessionid != null) { sendConsoleText('Self Update failed: ' + e.toString(), sessionid) }
        sendAgentMessage('Self Update failed: ' + e.toString(), 3);
        return;
    }

    var i;
    var path = require('_GenericMarshal').CreateVariable(process.execPath);
    var argarr = [process.execPath];
    var args;
    var options = require('MeshAgent').getStartupOptions();
    for (i in options) {
        argarr.push('--' + i + '="' + options[i] + '"');
    }
    args = require('_GenericMarshal').CreateVariable((1 + argarr.length) * require('_GenericMarshal').PointerSize);
    for (i = 0; i < argarr.length; ++i) {
        var arg = require('_GenericMarshal').CreateVariable(argarr[i]);
        arg.pointerBuffer().copy(args.toBuffer(), i * require('_GenericMarshal').PointerSize);
    }

    if (sessionid != null) { sendConsoleText('Restarting service via execv()', sessionid) }

    var descriptors = getOpenDescriptors();
    closeDescriptors(libc, descriptors);

    libc.execv(path, args);
    if (sessionid != null) { sendConsoleText('Self Update failed because execv() failed', sessionid) }
    sendAgentMessage('Self Update failed because execv() failed', 3);
}

function windows_execve(name, agentfilename, sessionid) {
    var libc;
    try {
        libc = require('_GenericMarshal').CreateNativeProxy('msvcrt.dll');
        libc.CreateMethod('_wexecve');
    }
    catch (xx) {
        sendConsoleText('Self Update failed because msvcrt.dll is missing', sessionid);
        sendAgentMessage('Self Update failed because msvcrt.dll is missing', 3);
        return;
    }

    var cmd = require('_GenericMarshal').CreateVariable(process.env['windir'] + '\\system32\\cmd.exe', { wide: true });
    var args = require('_GenericMarshal').CreateVariable(3 * require('_GenericMarshal').PointerSize);
    var arg1 = require('_GenericMarshal').CreateVariable('cmd.exe', { wide: true });
    var arg2 = require('_GenericMarshal').CreateVariable('/C wmic service "' + name + '" call stopservice & copy "' + process.cwd() + agentfilename + '.update" "' + process.execPath + '" & wmic service "' + name + '" call startservice & erase "' + process.cwd() + agentfilename + '.update"', { wide: true });

    arg1.pointerBuffer().copy(args.toBuffer());
    arg2.pointerBuffer().copy(args.toBuffer(), require('_GenericMarshal').PointerSize);

    libc._wexecve(cmd, args, 0);
}

// Start a JavaScript based Agent Self-Update
function agentUpdate_Start(updateurl, updateoptions)
{
    // If this value is null
    var sessionid = (updateoptions != null) ? updateoptions.sessionid : null; // If this is null, messages will be broadcast. Otherwise they will be unicasted

    if (agentUpdate_Start._selfupdate != null)
    {
        // We were already called, so we will ignore this duplicate request
        if (sessionid != null) { sendConsoleText('Self update already in progress...', sessionid); }
    }
    else
    {
        if (agentUpdate_Start._retryCount == null) { agentUpdate_Start._retryCount = 0; }
        if (require('MeshAgent').ARCHID == null && updateurl == null)
        {
            // This agent doesn't have the ability to tell us which ARCHID it is, so we don't know which agent to pull
            sendConsoleText('Unable to initiate update, agent ARCHID is not defined', sessionid);
        }
        else
        {
            var agentfilename = process.execPath.split(process.platform == 'win32' ? '\\' : '/').pop(); // Local File Name, ie: MeshAgent.exe
            var name = require('MeshAgent').serviceName;
            if (name == null) { name = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'; }      // This is an older agent that doesn't expose the service name, so use the default
            try
            {
                var s = require('service-manager').manager.getService(name);
                if (!s.isMe())
                {
                    if (process.platform == 'win32') { s.close(); }
                    sendConsoleText('Self Update cannot continue, this agent is not an instance of (' + name + ')', sessionid);
                    return;
                }
                if (process.platform == 'win32') { s.close(); }
            }
            catch (zz)
            {
                sendConsoleText('Self Update Failed because this agent is not an instance of (' + name + ')', sessionid);
                sendAgentMessage('Self Update Failed because this agent is not an instance of (' + name + ')', 3);
                return;
            }

            if (sessionid != null) { sendConsoleText('Downloading update...', sessionid); }
            var options = require('http').parseUri(updateurl != null ? updateurl : require('MeshAgent').ServerUrl);
            options.protocol = 'https:';
            if (updateurl == null) { options.path = ('/meshagents?id=' + require('MeshAgent').ARCHID); }
            options.rejectUnauthorized = false;
            options.checkServerIdentity = function checkServerIdentity(certs)
            {
                // If the tunnel certificate matches the control channel certificate, accept the connection
                try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
                try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }

                // Check that the certificate is the one expected by the server, fail if not.
                if (checkServerIdentity.servertlshash == null)
                {
                    if (require('MeshAgent').ServerInfo == null || require('MeshAgent').ServerInfo.ControlChannelCertificate == null) { return; }

                    sendConsoleText('Self Update failed, because the url cannot be verified', sessionid);
                    sendAgentMessage('Self Update failed, because the url cannot be verified', 3);
                    throw new Error('BadCert');
                }
                if (certs[0].digest == null) { return; }
                if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase()))
                {
                    sendConsoleText('Self Update failed, because the supplied certificate does not match', sessionid);
                    sendAgentMessage('Self Update failed, because the supplied certificate does not match', 3);
                    throw new Error('BadCert')
                }
            }
            options.checkServerIdentity.servertlshash = (updateoptions != null ? updateoptions.tlshash : null);
            agentUpdate_Start._selfupdate = require('https').get(options);
            agentUpdate_Start._selfupdate.on('error', function (e)
            {
                sendConsoleText('Self Update failed, because there was a problem trying to download the update', sessionid);
                sendAgentMessage('Self Update failed, because there was a problem trying to download the update', 3);
                agentUpdate_Start._selfupdate = null;
            });
            agentUpdate_Start._selfupdate.on('response', function (img)
            {
                this._file = require('fs').createWriteStream(agentfilename + '.update', { flags: 'wb' });
                this._filehash = require('SHA384Stream').create();
                this._filehash.on('hash', function (h)
                {
                    if (updateoptions != null && updateoptions.hash != null)
                    {
                        if (updateoptions.hash.toLowerCase() == h.toString('hex').toLowerCase())
                        {
                            if (sessionid != null) { sendConsoleText('Download complete. HASH verified.', sessionid); }
                        }
                        else
                        {
                            agentUpdate_Start._retryCount++;
                            sendConsoleText('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + ')', sessionid);
                            sendAgentMessage('Self Update FAILED because the downloaded agent FAILED hash check (' + agentUpdate_Start._retryCount + ')', 3);
                            agentUpdate_Start._selfupdate = null;

                            if (agentUpdate_Start._retryCount < 4)
                            {
                                // Retry the download again
                                sendConsoleText('Self Update will try again in 60 seconds...', sessionid);
                                agentUpdate_Start._timeout = setTimeout(agentUpdate_Start, 60000, updateurl, updateoptions);
                            }
                            else
                            {
                                sendConsoleText('Self Update giving up, too many failures...', sessionid);
                                sendAgentMessage('Self Update giving up, too many failures...', 3);
                            }
                            return;
                        }
                    }
                    else
                    {
                        sendConsoleText('Download complete. HASH=' + h.toString('hex'), sessionid);
                    }

                    // Send an indication to the server that we got the update download correctly.
                    try { require('MeshAgent').SendCommand({ action: 'agentupdatedownloaded' }); } catch (e) { }

                    if (sessionid != null) { sendConsoleText('Updating and restarting agent...', sessionid); }
                    if (process.platform == 'win32')
                    {
                        // Use _wexecve() equivalent to perform the update
                        windows_execve(name, agentfilename, sessionid);
                    }
                    else
                    {
                        var m = require('fs').statSync(process.execPath).mode;
                        require('fs').chmodSync(process.cwd() + agentfilename + '.update', m);

                        // remove binary
                        require('fs').unlinkSync(process.execPath);

                        // copy update
                        require('fs').copyFileSync(process.cwd() + agentfilename + '.update', process.execPath);
                        require('fs').chmodSync(process.execPath, m);

                        // erase update
                        require('fs').unlinkSync(process.cwd() + agentfilename + '.update');

                        switch (process.platform)
                        {
                            case 'freebsd':
                                bsd_execv(name, agentfilename, sessionid);
                                break;
                            case 'linux':
                                linux_execv(name, agentfilename, sessionid);
                                break;
                            default:
                                try
                                {
                                    // restart service
                                    var s = require('service-manager').manager.getService(name);
                                    s.restart();
                                }
                                catch (zz)
                                {
                                    sendConsoleText('Self Update encountered an error trying to restart service', sessionid);
                                    sendAgentMessage('Self Update encountered an error trying to restart service', 3);
                                }
                                break;
                        }
                    }
                });
                img.pipe(this._file);
                img.pipe(this._filehash);
            });
        }
    }
}

// Return p number of spaces 
function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }

setInterval(function () { sendConsoleText('Timer!'); }, 2000);

var path =
    {
        join: function () {
            var x = [];
            for (var i in arguments) {
                var w = arguments[i];
                if (w != null) {
                    while (w.endsWith('/') || w.endsWith('\\')) { w = w.substring(0, w.length - 1); }
                    if (i != 0) { while (w.startsWith('/') || w.startsWith('\\')) { w = w.substring(1); } }
                    x.push(w);
                }
            }
            if (x.length == 0) return '/';
            return x.join('/');
        }
    };
// Convert an object to string with all functions
function objToString(x, p, pad, ret) {
    if (ret == undefined) ret = '';
    if (p == undefined) p = 0;
    if (x == null) { return '[null]'; }
    if (p > 8) { return '[...]'; }
    if (x == undefined) { return '[undefined]'; }
    if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
    if (typeof x == 'buffer') { return '[buffer]'; }
    if (typeof x != 'object') { return x; }
    var r = '{' + (ret ? '\r\n' : ' ');
    for (var i in x) { if (i != '_ObjectID') { r += (addPad(p + 2, pad) + i + ': ' + objToString(x[i], p + 2, pad, ret) + (ret ? '\r\n' : ' ')); } }
    return r + addPad(p, pad) + '}';
}

// Split a string taking into account the quoats. Used for command line parsing
function splitArgs(str) {
    var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
    do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
    return myArray;
}

// Parse arguments string array into an object
function parseArgs(argv) {
    var results = { '_': [] }, current = null;
    for (var i = 1, len = argv.length; i < len; i++) {
        var x = argv[i];
        if (x.length > 2 && x[0] == '-' && x[1] == '-') {
            if (current != null) { results[current] = true; }
            current = x.substring(2);
        } else {
            if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
        }
    }
    if (current != null) { results[current] = true; }
    return results;
}

// Get server target url with a custom path
function getServerTargetUrl(path) {
    var x = require('MeshAgent').ServerUrl;
    //sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
    if (x == null) { return null; }
    if (path == null) { path = ''; }
    x = http.parseUri(x);
    if (x == null) return null;
    return x.protocol + '//' + x.host + ':' + x.port + '/' + path;
}

// Get server url. If the url starts with "*/..." change it, it not use the url as is.
function getServerTargetUrlEx(url) {
    if (url.substring(0, 2) == '*/') { return getServerTargetUrl(url.substring(2)); }
    return url;
}

require('MeshAgent').on('Connected', function () {
    require('os').name().then(function (v) {
        //sendConsoleText("Mesh Agent Recovery Console, OS: " + v);
        require('MeshAgent').SendCommand(meshCoreObj);
    });
});

// Called when receiving control data on websocket
function onTunnelControlData(data, ws) {
    var obj;
    if (ws == null) { ws = this; }
    if (typeof data == 'string') { try { obj = JSON.parse(data); } catch (e) { sendConsoleText('Invalid control JSON: ' + data); return; } }
    else if (typeof data == 'object') { obj = data; } else { return; }
    //sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
    //console.log('onTunnelControlData: ' + JSON.stringify(data));

    if (obj.action) {
        switch (obj.action) {
            case 'lock': {
                // Lock the current user out of the desktop
                try {
                    if (process.platform == 'win32') {
                        MeshServerLog("Locking remote user out of desktop", ws.httprequest);
                        var child = require('child_process');
                        child.execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/c', 'RunDll32.exe user32.dll,LockWorkStation'], { type: 1 });
                    }
                } catch (e) { }
                break;
            }
            default:
                // Unknown action, ignore it.
                break;
        }
        return;
    }

    switch (obj.type) {
        case 'options': {
            // These are additional connection options passed in the control channel.
            //sendConsoleText('options: ' + JSON.stringify(obj));
            delete obj.type;
            ws.httprequest.xoptions = obj;

            // Set additional user consent options if present
            if ((obj != null) && (typeof obj.consent == 'number')) { ws.httprequest.consent |= obj.consent; }

            break;
        }
        case 'close': {
            // We received the close on the websocket
            //sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
            try { ws.close(); } catch (e) { }
            break;
        }
        case 'termsize': {
            // Indicates a change in terminal size
            if (process.platform == 'win32') {
                if (ws.httprequest._dispatcher == null) return;
                if (ws.httprequest._dispatcher.invoke) { ws.httprequest._dispatcher.invoke('resizeTerminal', [obj.cols, obj.rows]); }
            }
            else {
                if (ws.httprequest.process == null || ws.httprequest.process.pty == 0) return;
                if (ws.httprequest.process.tcsetsize) { ws.httprequest.process.tcsetsize(obj.rows, obj.cols); }
            }
            break;
        }
    }
}


require('MeshAgent').AddCommandHandler(function (data) {
    if (typeof data == 'object') {
        // If this is a console command, parse it and call the console handler
        switch (data.action) {
            case 'agentupdate':
                agentUpdate_Start(data.url, { hash: data.hash, tlshash: data.servertlshash, sessionid: data.sessionid });
                break;
            case 'msg':
                {
                    switch (data.type) {
                        case 'console': { // Process a console command
                            if (data.value && data.sessionid) {
                                var args = splitArgs(data.value);
                                processConsoleCommand(args[0].toLowerCase(), parseArgs(args), data.rights, data.sessionid);
                            }
                            break;
                        }
                        case 'tunnel':
                            {
                                if (data.value != null) { // Process a new tunnel connection request
                                    // Create a new tunnel object
                                    if (data.rights != 4294967295) {
                                        MeshServerLog('Tunnel Error: RecoveryCore requires admin rights for tunnels');
                                        break;
                                    }

                                    var xurl = getServerTargetUrlEx(data.value);
                                    if (xurl != null) {
                                        var woptions = http.parseUri(xurl);
                                        woptions.rejectUnauthorized = 0;
                                        woptions.perMessageDeflate = false;
                                        woptions.checkServerIdentity = function checkServerIdentity(certs) {
                                            // If the tunnel certificate matches the control channel certificate, accept the connection
                                            try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.digest == certs[0].digest) return; } catch (ex) { }
                                            try { if (require('MeshAgent').ServerInfo.ControlChannelCertificate.fingerprint == certs[0].fingerprint) return; } catch (ex) { }

                                            // Check that the certificate is the one expected by the server, fail if not.
                                            if ((checkServerIdentity.servertlshash != null) && (checkServerIdentity.servertlshash.toLowerCase() != certs[0].digest.split(':').join('').toLowerCase())) { throw new Error('BadCert') }
                                        }
                                        woptions.checkServerIdentity.servertlshash = data.servertlshash;


                                        //sendConsoleText(JSON.stringify(woptions));
                                        var tunnel = http.request(woptions);
                                        tunnel.on('upgrade', function (response, s, head) {
                                            if (require('MeshAgent').idleTimeout != null) {
                                                s.setTimeout(require('MeshAgent').idleTimeout * 1000);
                                                s.on('timeout', function () {
                                                    this.ping();
                                                    this.setTimeout(require('MeshAgent').idleTimeout * 1000);
                                                });
                                            }

                                            this.s = s;
                                            s.httprequest = this;
                                            s.tunnel = this;
                                            s.on('end', function () {
                                                if (tunnels[this.httprequest.index] == null) return; // Stop duplicate calls.

                                                // If there is a upload or download active on this connection, close the file
                                                if (this.httprequest.uploadFile) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; delete this.httprequest.uploadFileid; delete this.httprequest.uploadFilePath; }
                                                if (this.httprequest.downloadFile) { delete this.httprequest.downloadFile; }

                                                //sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
                                                delete tunnels[this.httprequest.index];

                                                // Clean up WebSocket
                                                this.removeAllListeners('data');
                                            });
                                            s.on('data', function (data) {
                                                // If this is upload data, save it to file
                                                if ((this.httprequest.uploadFile) && (typeof data == 'object') && (data[0] != 123)) {
                                                    // Save the data to file being uploaded.
                                                    if (data[0] == 0) {
                                                        // If data starts with zero, skip the first byte. This is used to escape binary file data from JSON.
                                                        try { fs.writeSync(this.httprequest.uploadFile, data, 1, data.length - 1); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
                                                    } else {
                                                        // If data does not start with zero, save as-is.
                                                        try { fs.writeSync(this.httprequest.uploadFile, data); } catch (e) { sendConsoleText('FileUpload Error'); this.write(Buffer.from(JSON.stringify({ action: 'uploaderror' }))); return; } // Write to the file, if there is a problem, error out.
                                                    }
                                                    this.write(Buffer.from(JSON.stringify({ action: 'uploadack', reqid: this.httprequest.uploadFileid }))); // Ask for more data.
                                                    return;
                                                }

                                                if (this.httprequest.state == 0) {
                                                    // Check if this is a relay connection
                                                    if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
                                                }
                                                else {
                                                    // Handle tunnel data
                                                    if (this.httprequest.protocol == 0) {   // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer
                                                        // Take a look at the protocol
                                                        if ((data.length > 3) && (data[0] == '{')) { onTunnelControlData(data, this); return; }
                                                        this.httprequest.protocol = parseInt(data);
                                                        if (typeof this.httprequest.protocol != 'number') { this.httprequest.protocol = 0; }
                                                        if (this.httprequest.protocol == 10) {
                                                            //
                                                            // Basic file transfer
                                                            //
                                                            var stats = null;
                                                            if ((process.platform != 'win32') && (this.httprequest.xoptions.file.startsWith('/') == false)) { this.httprequest.xoptions.file = '/' + this.httprequest.xoptions.file; }
                                                            try { stats = require('fs').statSync(this.httprequest.xoptions.file) } catch (e) { }
                                                            try { if (stats) { this.httprequest.downloadFile = fs.createReadStream(this.httprequest.xoptions.file, { flags: 'rbN' }); } } catch (e) { }
                                                            if (this.httprequest.downloadFile) {
                                                                //sendConsoleText('BasicFileTransfer, ok, ' + this.httprequest.xoptions.file + ', ' + JSON.stringify(stats));
                                                                this.write(JSON.stringify({ op: 'ok', size: stats.size }));
                                                                this.httprequest.downloadFile.pipe(this);
                                                                this.httprequest.downloadFile.end = function () { }
                                                            } else {
                                                                //sendConsoleText('BasicFileTransfer, cancel, ' + this.httprequest.xoptions.file);
                                                                this.write(JSON.stringify({ op: 'cancel' }));
                                                            }
                                                        }
                                                        else if ((this.httprequest.protocol == 1) || (this.httprequest.protocol == 6) || (this.httprequest.protocol == 8) || (this.httprequest.protocol == 9)) {
                                                            //
                                                            // Remote Terminal
                                                            //
                                                            if (process.platform == "win32") {
                                                                var cols = 80, rows = 25;
                                                                if (this.httprequest.xoptions) {
                                                                    if (this.httprequest.xoptions.rows) { rows = this.httprequest.xoptions.rows; }
                                                                    if (this.httprequest.xoptions.cols) { cols = this.httprequest.xoptions.cols; }
                                                                }

                                                                // Admin Terminal
                                                                if (require('win-virtual-terminal').supported) {
                                                                    // ConPTY PseudoTerminal
                                                                    // this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);

                                                                    // The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
                                                                    this.httprequest._dispatcher = require('win-dispatcher').dispatch({ modules: [{ name: 'win-virtual-terminal', script: getJSModule('win-virtual-terminal') }], launch: { module: 'win-virtual-terminal', method: 'Start', args: [cols, rows] } });
                                                                    this.httprequest._dispatcher.ws = this;
                                                                    this.httprequest._dispatcher.on('connection', function (c) {
                                                                        this.ws._term = c;
                                                                        c.pipe(this.ws, { dataTypeSkip: 1 });
                                                                        this.ws.pipe(c, { dataTypeSkip: 1 });
                                                                    });
                                                                }
                                                                else {
                                                                    // Legacy Terminal
                                                                    this.httprequest._term = require('win-terminal').Start(80, 25);
                                                                    this.httprequest._term.pipe(this, { dataTypeSkip: 1 });
                                                                    this.pipe(this.httprequest._term, { dataTypeSkip: 1, end: false });
                                                                    this.prependListener('end', function () { this.httprequest._term.end(function () { sendConsoleText('Terminal was closed'); }); });
                                                                }
                                                            }
                                                            else {
                                                                var env = { HISTCONTROL: 'ignoreboth' };
                                                                if (this.httprequest.xoptions) {
                                                                    if (this.httprequest.xoptions.rows) { env.LINES = ('' + this.httprequest.xoptions.rows); }
                                                                    if (this.httprequest.xoptions.cols) { env.COLUMNS = ('' + this.httprequest.xoptions.cols); }
                                                                }
                                                                var options = { type: childProcess.SpawnTypes.TERM, env: env };

                                                                if (require('fs').existsSync('/bin/bash')) {
                                                                    this.httprequest.process = childProcess.execFile('/bin/bash', ['bash'], options); // Start bash
                                                                }
                                                                else {
                                                                    this.httprequest.process = childProcess.execFile('/bin/sh', ['sh'], options); // Start sh
                                                                }

                                                                // Spaces at the beginning of lines are needed to hide commands from the command history
                                                                if (process.platform == 'linux') { this.httprequest.process.stdin.write(' alias ls=\'ls --color=auto\';clear\n'); }
                                                                this.httprequest.process.tunnel = this;
                                                                this.httprequest.process.on('exit', function (ecode, sig) { this.tunnel.end(); });
                                                                this.httprequest.process.stderr.on('data', function (chunk) { this.parent.tunnel.write(chunk); });
                                                                this.httprequest.process.stdout.pipe(this, { dataTypeSkip: 1 }); // 0 = Binary, 1 = Text.
                                                                this.pipe(this.httprequest.process.stdin, { dataTypeSkip: 1, end: false }); // 0 = Binary, 1 = Text.
                                                                this.prependListener('end', function () { this.httprequest.process.kill(); });
                                                            }
                                                        }
                                                    }
                                                    else if (this.httprequest.protocol == 5) {
                                                        // Process files commands
                                                        var cmd = null;
                                                        try { cmd = JSON.parse(data); } catch (e) { };
                                                        if (cmd == null) { return; }
                                                        if ((cmd.ctrlChannel == '102938') || ((cmd.type == 'offer') && (cmd.sdp != null))) { return; } // If this is control data, handle it now.
                                                        if (cmd.action == undefined) { return; }
                                                        console.log('action: ', cmd.action);

                                                        //sendConsoleText('CMD: ' + JSON.stringify(cmd));

                                                        if ((cmd.path != null) && (process.platform != 'win32') && (cmd.path[0] != '/')) { cmd.path = '/' + cmd.path; } // Add '/' to paths on non-windows
                                                        //console.log(objToString(cmd, 0, ' '));
                                                        switch (cmd.action) {
                                                            case 'ls':
                                                                // Send the folder content to the browser
                                                                var response = getDirectoryInfo(cmd.path);
                                                                if (cmd.reqid != undefined) { response.reqid = cmd.reqid; }
                                                                this.write(Buffer.from(JSON.stringify(response)));
                                                                break;
                                                            case 'mkdir':
                                                                {
                                                                    // Create a new empty folder
                                                                    fs.mkdirSync(cmd.path);
                                                                    break;
                                                                }
                                                            case 'rm':
                                                                {
                                                                    // Delete, possibly recursive delete
                                                                    for (var i in cmd.delfiles) {
                                                                        try { deleteFolderRecursive(path.join(cmd.path, cmd.delfiles[i]), cmd.rec); } catch (e) { }
                                                                    }
                                                                    break;
                                                                }
                                                            case 'rename':
                                                                {
                                                                    // Rename a file or folder
                                                                    var oldfullpath = path.join(cmd.path, cmd.oldname);
                                                                    var newfullpath = path.join(cmd.path, cmd.newname);
                                                                    try { fs.renameSync(oldfullpath, newfullpath); } catch (e) { console.log(e); }
                                                                    break;
                                                                }
                                                            case 'findfile':
                                                                {
                                                                    // Search for files
                                                                    var r = require('file-search').find('"' + cmd.path + '"', cmd.filter);
                                                                    if (!r.cancel) { r.cancel = function cancel() { this.child.kill(); }; }
                                                                    this._search = r;
                                                                    r.socket = this;
                                                                    r.socket.reqid = cmd.reqid; // Search request id. This is used to send responses and cancel the request.
                                                                    r.socket.path = cmd.path;   // Search path
                                                                    r.on('result', function (str) { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: str.substring(this.socket.path.length), reqid: this.socket.reqid }))); } catch (ex) { } });
                                                                    r.then(function () { try { this.socket.write(Buffer.from(JSON.stringify({ action: 'findfile', r: null, reqid: this.socket.reqid }))); } catch (ex) { } });
                                                                    break;
                                                                }
                                                            case 'cancelfindfile':
                                                                {
                                                                    if (this._search) { this._search.cancel(); this._search = null; }
                                                                    break;
                                                                }
                                                            case 'download':
                                                                {
                                                                    // Download a file
                                                                    var sendNextBlock = 0;
                                                                    if (cmd.sub == 'start') { // Setup the download
                                                                        if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path.
                                                                            if (process.platform == 'win32') {
                                                                                if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; }
                                                                            } else {
                                                                                if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; }
                                                                            }
                                                                        }
                                                                        MeshServerLogEx((cmd.ask == 'coredump') ? 104 : 49, [cmd.path], 'Download: \"' + cmd.path + '\"', this.httprequest);
                                                                        if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
                                                                        this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 }
                                                                        try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (e) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; }
                                                                        if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); }
                                                                    } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands
                                                                        if (cmd.sub == 'startack') { sendNextBlock = ((typeof cmd.ack == 'number') ? cmd.ack : 8); } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; }
                                                                    }
                                                                    // Send the next download block(s)
                                                                    while (sendNextBlock > 0) {
                                                                        sendNextBlock--;
                                                                        var buf = Buffer.alloc(16384);
                                                                        var len = fs.readSync(this.filedownload.f, buf, 4, 16380, null);
                                                                        this.filedownload.ptr += len;
                                                                        if (len < 16380) { buf.writeInt32BE(0x01000001, 0); fs.closeSync(this.filedownload.f); delete this.filedownload; sendNextBlock = 0; } else { buf.writeInt32BE(0x01000000, 0); }
                                                                        this.write(buf.slice(0, len + 4)); // Write as binary
                                                                    }
                                                                    break;
                                                                }
                                                            case 'upload':
                                                                {
                                                                    // Upload a file, browser to agent
                                                                    if (this.httprequest.uploadFile != null) { fs.closeSync(this.httprequest.uploadFile); delete this.httprequest.uploadFile; }
                                                                    if (cmd.path == undefined) break;
                                                                    var filepath = cmd.name ? pathjoin(cmd.path, cmd.name) : cmd.path;
                                                                    this.httprequest.uploadFilePath = filepath;
                                                                    MeshServerLogEx(50, [filepath], 'Upload: \"' + filepath + '\"', this.httprequest);
                                                                    try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
                                                                    this.httprequest.uploadFileid = cmd.reqid;
                                                                    if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
                                                                    break;
                                                                }
                                                            case 'uploaddone':
                                                                {
                                                                    // Indicates that an upload is done
                                                                    if (this.httprequest.uploadFile) {
                                                                        fs.closeSync(this.httprequest.uploadFile);
                                                                        this.write(Buffer.from(JSON.stringify({ action: 'uploaddone', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
                                                                        delete this.httprequest.uploadFile;
                                                                        delete this.httprequest.uploadFileid;
                                                                        delete this.httprequest.uploadFilePath;
                                                                    }
                                                                    break;
                                                                }
                                                            case 'uploadcancel':
                                                                {
                                                                    // Indicates that an upload is canceled
                                                                    if (this.httprequest.uploadFile) {
                                                                        fs.closeSync(this.httprequest.uploadFile);
                                                                        fs.unlinkSync(this.httprequest.uploadFilePath);
                                                                        this.write(Buffer.from(JSON.stringify({ action: 'uploadcancel', reqid: this.httprequest.uploadFileid }))); // Indicate that we closed the file.
                                                                        delete this.httprequest.uploadFile;
                                                                        delete this.httprequest.uploadFileid;
                                                                        delete this.httprequest.uploadFilePath;
                                                                    }
                                                                    break;
                                                                }
                                                            case 'copy': {
                                                                // Copy a bunch of files from scpath to dspath
                                                                for (var i in cmd.names) {
                                                                    var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
                                                                    if (sc != ds) { try { fs.copyFileSync(sc, ds); } catch (e) { } }
                                                                }
                                                                break;
                                                            }
                                                            case 'move': {
                                                                // Move a bunch of files from scpath to dspath
                                                                for (var i in cmd.names) {
                                                                    var sc = path.join(cmd.scpath, cmd.names[i]), ds = path.join(cmd.dspath, cmd.names[i]);
                                                                    if (sc != ds) { try { fs.copyFileSync(sc, ds); fs.unlinkSync(sc); } catch (e) { } }
                                                                }
                                                                break;
                                                            }
                                                        }
                                                    }
                                                }
                                            });
                                        });
                                        tunnel.onerror = function (e) { sendConsoleText("ERROR: " + JSON.stringify(e)); }
                                        tunnel.sessionid = data.sessionid;
                                        tunnel.rights = data.rights;
                                        tunnel.state = 0;
                                        tunnel.url = xurl;
                                        tunnel.protocol = 0;
                                        tunnel.tcpaddr = data.tcpaddr;
                                        tunnel.tcpport = data.tcpport;
                                        tunnel.end();
                                        // Put the tunnel in the tunnels list
                                        var index = nextTunnelIndex++;
                                        tunnel.index = index;
                                        tunnels[index] = tunnel;

                                        //sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
                                    }
                                }
                                break;
                            }

                        default:
                            // Unknown action, ignore it.
                            break;
                    }
                    break;
                }
            default:
                // Unknown action, ignore it.
                break;
        }
    }
});

function processConsoleCommand(cmd, args, rights, sessionid) {
    try {
        var response = null;
        switch (cmd) {
            case 'help':
                response = "Available commands are: agentupdate, dbkeys, dbget, dbset, dbcompact, eval, netinfo, osinfo, setdebug, versions.";
                break;
            case '_descriptors':
                response = 'Open Descriptors: ' + JSON.stringify(getOpenDescriptors());
                break;
            case 'versions':
                response = JSON.stringify(process.versions, null, '  ');
                break;
            case 'agentupdate':
                // Request that the server send a agent update command
                require('MeshAgent').SendCommand({ action: 'agentupdate', sessionid: sessionid });
                break;
            case 'agentupdateex':
                // Perform an direct agent update without requesting any information from the server, this should not typically be used.
                agentUpdate_Start(null, { sessionid: sessionid });
                break;
            case 'eval':
                { // Eval JavaScript
                    if (args['_'].length < 1) {
                        response = 'Proper usage: eval "JavaScript code"'; // Display correct command usage
                    } else {
                        response = JSON.stringify(require('MeshAgent').eval(args['_'][0])); // This can only be run by trusted administrator.
                    }
                    break;
                }
            case 'setdebug':
                {
                    if (args['_'].length < 1) { response = 'Proper usage: setdebug (target), 0 = Disabled, 1 = StdOut, 2 = This Console, * = All Consoles, 4 = WebLog, 8 = Logfile'; } // Display usage
                    else { if (args['_'][0] == '*') { console.setDestination(2); } else { console.setDestination(parseInt(args['_'][0]), sessionid); } }
                    break;
                }
            case 'osinfo': { // Return the operating system information
                var i = 1;
                if (args['_'].length > 0) { i = parseInt(args['_'][0]); if (i > 8) { i = 8; } response = 'Calling ' + i + ' times.'; }
                for (var j = 0; j < i; j++) {
                    var pr = require('os').name();
                    pr.sessionid = sessionid;
                    pr.then(function (v) {
                        sendConsoleText("OS: " + v + (process.platform == 'win32' ? (require('win-virtual-terminal').supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]') : ''), this.sessionid);
                    });
                }
                break;
            }
            case 'dbkeys': { // Return all data store keys
                response = JSON.stringify(db.Keys);
                break;
            }
            case 'dbget': { // Return the data store value for a given key
                if (db == null) { response = "Database not accessible."; break; }
                if (args['_'].length != 1) {
                    response = "Proper usage: dbget (key)"; // Display the value for a given database key
                } else {
                    response = db.Get(args['_'][0]);
                }
                break;
            }
            case 'dbset': { // Set a data store key and value pair
                if (db == null) { response = "Database not accessible."; break; }
                if (args['_'].length != 2) {
                    response = "Proper usage: dbset (key) (value)"; // Set a database key
                } else {
                    var r = db.Put(args['_'][0], args['_'][1]);
                    response = "Key set: " + r;
                }
                break;
            }
            case 'dbcompact': { // Compact the data store
                if (db == null) { response = "Database not accessible."; break; }
                var r = db.Compact();
                response = "Database compacted: " + r;
                break;
            }
            case 'tunnels': { // Show the list of current tunnels
                response = '';
                for (var i in tunnels) { response += "Tunnel #" + i + ", " + tunnels[i].url + '\r\n'; }
                if (response == '') { response = "No websocket sessions."; }
                break;
            }
            case 'netinfo': { // Show network interface information
                //response = objToString(mesh.NetInfo, 0, ' ');
                var interfaces = require('os').networkInterfaces();
                response = objToString(interfaces, 0, ' ', true);
                break;
            }
            default: { // This is an unknown command, return an error message
                response = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.';
                break;
            }
        }
    } catch (e) { response = "Command returned an exception error: " + e; console.log(e); }
    if (response != null) { sendConsoleText(response, sessionid); }
}

// Get a formated response for a given directory path
function getDirectoryInfo(reqpath) {
    var response = { path: reqpath, dir: [] };
    if (((reqpath == undefined) || (reqpath == '')) && (process.platform == 'win32')) {
        // List all the drives in the root, or the root itself
        var results = null;
        try { results = fs.readDrivesSync(); } catch (e) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
        if (results != null) {
            for (var i = 0; i < results.length; ++i) {
                var drive = { n: results[i].name, t: 1 };
                if (results[i].type == 'REMOVABLE') { drive.dt = 'removable'; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
                response.dir.push(drive);
            }
        }
    } else {
        // List all the files and folders in this path
        if (reqpath == '') { reqpath = '/'; }
        var results = null, xpath = path.join(reqpath, '*');
        //if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
        try { results = fs.readdirSync(xpath); } catch (e) { }
        if (results != null) {
            for (var i = 0; i < results.length; ++i) {
                if ((results[i] != '.') && (results[i] != '..')) {
                    var stat = null, p = path.join(reqpath, results[i]);
                    //if (process.platform == "win32") { p = p.split('/').join('\\'); }
                    try { stat = fs.statSync(p); } catch (e) { } // TODO: Get file size/date
                    if ((stat != null) && (stat != undefined)) {
                        if (stat.isDirectory() == true) {
                            response.dir.push({ n: results[i], t: 2, d: stat.mtime });
                        } else {
                            response.dir.push({ n: results[i], t: 3, s: stat.size, d: stat.mtime });
                        }
                    }
                }
            }
        }
    }
    return response;
}
// Delete a directory with a files and directories within it
function deleteFolderRecursive(path, rec) {
    if (fs.existsSync(path)) {
        if (rec == true) {
            fs.readdirSync(path.join(path, '*')).forEach(function (file, index) {
                var curPath = path.join(path, file);
                if (fs.statSync(curPath).isDirectory()) { // recurse
                    deleteFolderRecursive(curPath, true);
                } else { // delete file
                    fs.unlinkSync(curPath);
                }
            });
        }
        fs.unlinkSync(path);
    }
};