'use strict'; var sqlite3 = require('sqlite3').verbose(); var fs = require('fs'); var async = require('async'); function blobToIPv4(b) { if (!b) return null; if (b.length !== 16) return null; return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString(); } function blobToIPv6(b) { if (!b) return null; if (b.length !== 16) return null; var s = ''; for(var i=0;i<16;++i) { var x = b.readUInt8(i).toString(16); if (x.length === 1) s += '0'; s += x; if ((((i+1) & 1) === 0)&&(i !== 15)) s += ':'; } return s; } if (process.argv.length !== 4) { console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility'); console.log('(c)2017 ZeroTier, Inc. [GPL3]'); console.log(''); console.log('Usage: node migrate.js '); console.log(''); console.log('The first argument must be the path to the old Sqlite3 controller.db'); console.log('file. The second must be the path to the EMPTY controller.d database'); console.log('directory for a new (1.1.17 or newer) controller. If this path does'); console.log('not exist it will be created.'); console.log(''); console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.'); console.log('If your controller is old you should first upgrade to 1.1.14 and run the'); console.log('controller so that it will brings its Sqlite3 database up to the latest'); console.log('version before running this migration.'); console.log(''); process.exit(1); } var oldDbPath = process.argv[2]; var newDbPath = process.argv[3]; console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...'); console.log(''); var old = new sqlite3.Database(oldDbPath); var networks = {}; var nodeIdentities = {}; var networkCount = 0; var memberCount = 0; var routeCount = 0; var ipAssignmentPoolCount = 0; var ipAssignmentCount = 0; var ruleCount = 0; var oldSchemaVersion = -1; async.series([function(nextStep) { old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) { oldSchemaVersion = parseInt(row.v)||-1; },nextStep); },function(nextStep) { if (oldSchemaVersion !== 4) { console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old'); console.log('controller to 1.1.14 first and run it once to bring its DB up to date.'); return process.exit(1); } console.log('Reading networks...'); old.each('SELECT * FROM Network',function(err,row) { if ((typeof row.id === 'string')&&(row.id.length === 16)) { var flags = parseInt(row.flags)||0; networks[row.id] = { id: row.id, nwid: row.id, objtype: 'network', authTokens: [], capabilities: [], creationTime: parseInt(row.creationTime)||0, enableBroadcast: !!row.enableBroadcast, ipAssignmentPools: [], multicastLimit: row.multicastLimit||32, name: row.name||'', private: !!row.private, revision: parseInt(row.revision)||1, rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all routes: [], v4AssignMode: { 'zt': ((flags & 1) !== 0) }, v6AssignMode: { '6plane': ((flags & 4) !== 0), 'rfc4193': ((flags & 2) !== 0), 'zt': ((flags & 8) !== 0) }, _members: {} // temporary }; ++networkCount; //console.log(networks[row.id]); } },nextStep); },function(nextStep) { console.log(' '+networkCount+' networks.'); console.log('Reading network route definitions...'); old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { var network = networks[row.networkId]; if (network) { var rt = { target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits), via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null) }; network.routes.push(rt); ++routeCount; } },nextStep); },function(nextStep) { console.log(' '+routeCount+' routes in '+networkCount+' networks.'); console.log('Reading IP assignment pools...'); old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { var network = networks[row.networkId]; if (network) { var p = { ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)), ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd)) }; network.ipAssignmentPools.push(p); ++ipAssignmentPoolCount; } },nextStep); },function(nextStep) { console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.'); console.log('Reading known node identities...'); old.each('SELECT * FROM Node',function(err,row) { nodeIdentities[row.id] = row.identity; },nextStep); },function(nextStep) { console.log(' '+Object.keys(nodeIdentities).length+' known identities.'); console.log('Reading network members...'); old.each('SELECT * FROM Member',function(err,row) { var network = networks[row.networkId]; if (network) { network._members[row.nodeId] = { id: row.nodeId, address: row.nodeId, objtype: 'member', authorized: !!row.authorized, activeBridge: !!row.activeBridge, authHistory: [], capabilities: [], creationTime: 0, identity: nodeIdentities[row.nodeId]||null, ipAssignments: [], lastAuthorizedTime: (row.authorized) ? Date.now() : 0, lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(), lastRequestMetaData: '', noAutoAssignIps: false, nwid: row.networkId, revision: parseInt(row.memberRevision)||1, tags: [], recentLog: [] }; ++memberCount; //console.log(network._members[row.nodeId]); } },nextStep); },function(nextStep) { console.log(' '+memberCount+' members of '+networkCount+' networks.'); console.log('Reading static IP assignments...'); old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) { var network = networks[row.networkId]; if (network) { var member = network._members[row.nodeId]; if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts if (row.ipVersion == 4) { member.ipAssignments.push(blobToIPv4(row.ip)); ++ipAssignmentCount; } else if (row.ipVersion == 6) { member.ipAssignments.push(blobToIPv6(row.ip)); ++ipAssignmentCount; } } } },nextStep); },function(nextStep) { // Old versions only supported Ethertype whitelisting, so that's // all we mirror forward. The other fields were always unused. console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.'); console.log('Reading allowed Ethernet types (old basic rules)...'); var etherTypesByNetwork = {}; old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) { if (row.networkId in networks) { var et = parseInt(row.etherType)||0; var ets = etherTypesByNetwork[row.networkId]; if (!ets) etherTypesByNetwork[row.networkId] = [ et ]; else ets.push(et); } },function(err) { if (err) return nextStep(err); for(var nwid in etherTypesByNetwork) { var ets = etherTypesByNetwork[nwid].sort(); var network = networks[nwid]; if (network) { var rules = []; if (ets.indexOf(0) >= 0) { // If 0 is in the list, all Ethernet types are allowed so we accept all. rules.push({ 'type': 'ACTION_ACCEPT' }); } else { // Otherwise we whitelist. for(var i=0;i 0) { try { fs.mkdirSync(nwBase+network.id); } catch (e) {} var mbase = nwBase+network.id+'/member'; try { fs.mkdirSync(mbase,0o700); } catch (e) {} mbase = mbase + '/'; for(var mi=0;mi