/* * A JavaScript class based model for the ZeroTier controller microservice API * Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/ * * 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 . * * -- * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial closed-source software that incorporates or links * directly against ZeroTier software without disclosing the source code * of your own application. */ 'use strict'; /** * @param {string} IP with optional /netmask|port section * @return 4, 6, or 0 if invalid */ function ipClassify(ip) { if ((!ip)||(typeof ip !== 'string')) return 0; let ips = ip.split('/'); if (ips.length > 0) { if (ips.length > 1) { if (ips[1].length === 0) return 0; for(let i=0;i 0) { for(let i=0;i 0) { for(let i=0;i= 0) { r += c; if (r.length === l) break; } } while (r.length < l) r = '0' + r; return r; }; exports.formatZeroTierIdentifier = formatZeroTierIdentifier; /** * Goes through a rule set array and makes sure it's valid, returning a canonicalized version * * @param {array[object]} rules Array of ZeroTier rules * @return New array of canonicalized rules * @throws {Error} Rule set is invalid */ function formatRuleSetArray(rules) { let r = []; if ((rules)&&(Array.isArray(rules))) { for(let a=0;a= 0) this._authTokens[k] = exp; } } return this._authTokens; } get capabilities() { return this._capabilities; } set capabilities(c) { let ca = []; let ids = {}; if ((c)&&(Array.isArray(c))) { for(let a=0;a= 0)&&(capId <= 0xffffffff)&&(!ids[capId])) { ids[capId] = true; let capDefault = !!cap['default']; let capRules = formatRuleSetArray(cap.rules); ca.push({ id: capId, 'default': capDefault, rules: capRules }); } } } } ca.sort(function(a,b) { a = a.id; b = b.id; return ((a > b) ? 1 : ((a < b) ? -1 : 0)); }); this._capabilities = ca; return ca; } get ipAssignmentPools() { return this._ipAssignmentPools; } set ipAssignmentPools(ipp) { let pa = []; let ranges = {}; if ((ipp)&&(Array.isArray(ipp))) { for(let a=0;a 0)&&(stype === ipClassify(end))&&(!ranges[start+'_'+end])) { ranges[start+'_'+end] = true; pa.push({ ipRangeStart: start, ipRangeEnd: end }); } } } } } pa.sort(function(a,b) { return a.ipRangeStart.localeCompare(b.ipRangeStart); }); this._ipAssignmentPools = pa; return pa; } get multicastLimit() { return this._multicastLimit; } set multicastLimit(n) { try { let nn = parseInt(n)||0; this._multicastLimit = (nn >= 0) ? nn : 0; } catch (e) { this._multicastLimit = 0; } return this._multicastLimit; } get routes() { return this._routes; } set routes(r) { let ra = []; let targets = {}; if ((r)&&(Array.isArray(r))) { for(let a=0;a 0)&&((routeVia === null)||(ipClassify(routeVia) === rtt))&&(!targets[routeTarget])) { targets[routeTarget] = true; ra.push({ target: routeTarget, via: routeVia }); } } } } ra.sort(function(a,b) { return a.routeTarget.localeCompare(b.routeTarget); }); this._routes = ra; return ra; } get tags() { return this._tags; } set tags(t) { let ta = []; if ((t)&&(Array.isArray(t))) { for(let a=0;a= 0)||(tagId <= 0xffffffff)) { let tagDefault = tag.default; if (typeof tagDefault !== 'number') tagDefault = parseInt(tagDefault)||null; if ((tagDefault < 0)||(tagDefault > 0xffffffff)) tagDefault = null; ta.push({ 'id': tagId, 'default': tagDefault }); } } } } ta.sort(function(a,b) { a = a.id; b = b.id; return ((a > b) ? 1 : ((a < b) ? -1 : 0)); }); this._tags = ta; return ta; } get v4AssignMode() { return this._v4AssignMode; } set v4AssignMode(m) { if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) { this._v4AssignMode.zt = m.zt; } else if (m === 'zt') { // legacy this._v4AssignMode.zt = true; } else { this._v4AssignMode.zt = false; } } get v6AssignMode() { return this._v6AssignMode; } set v6AssignMode(m) { if ((m)&&(typeof m === 'object')&&(!Array.isArray(m))) { this._v6AssignMode.zt = m.zt; this._v6AssignMode.rfc4193 = m.rfc4193; this._v6AssignMode['6plane'] = m['6plane']; } else if (typeof m === 'string') { // legacy let ms = m.split(','); this._v6AssignMode.zt = false; this._v6AssignMode.rfc4193 = false; this._v6AssignMode['6plane'] = false; for(let i=0;i= 10000) mtu = 10000; // maximum as per ZT spec this._mtu = mtu; } get name() { return this._name; } set name(n) { if (typeof n === 'string') this._name = n; else if (typeof n === 'number') this._name = n.toString(); else this._name = ''; } get private() { return this._private; } set private(b) { // This is really meaningful for security, so make true unless explicitly set to false. this._private = (b !== false); } get activeMemberCount() { return this.__activeMemberCount; } get authorizedMemberCount() { return this.__authorizedMemberCount; } get totalMemberCount() { return this.__totalMemberCount; } get clock() { return this.__clock; } get creationTime() { return this.__creationTime; } get revision() { return this.__revision; } toJSONExcludeControllerGenerated() { return { id: this.id, objtype: 'network', nwid: this.nwid, authTokens: this.authTokens, capabilities: this.capabilities, ipAssignmentPools: this.ipAssignmentPools, multicastLimit: this.multicastLimit, routes: this.routes, tags: this.tags, v4AssignMode: this._v4AssignMode.toJSON(), v6AssignMode: this._v6AssignMode.toJSON(), rules: this.rules, enableBroadcast: this.enableBroadcast, mtu: this.mtu, name: this.name, 'private': this['private'] }; } toJSON() { var j = this.toJSONExcludeControllerGenerated(); j.activeMemberCount = this.activeMemberCount; j.authorizedMemberCount = this.authorizedMemberCount; j.totalMemberCount = this.totalMemberCount; j.clock = this.clock; j.creationTime = this.creationTime; j.revision = this.revision; return j; } clear() { this._id = ''; this._authTokens = {}; this._capabilities = []; this._ipAssignmentPools = []; this._multicastLimit = 32; this._routes = []; this._tags = []; this._v4AssignMode = new _V4AssignMode(); this._v6AssignMode = new _v6AssignMode(); this._rules = []; this._enableBroadcast = true; this._mtu = 2800; this._name = ''; this._private = true; this.__activeMemberCount = 0; this.__authorizedMemberCount = 0; this.__totalMemberCount = 0; this.__clock = 0; this.__creationTime = 0; this.__revision = 0; } patch(obj) { if (obj instanceof Network) obj = obj.toJSON(); if ((obj)&&(typeof obj === 'object')&&(!Array.isArray(obj))) { for(var k in obj) { try { switch(k) { case 'id': case 'authTokens': case 'capabilities': case 'ipAssignmentPools': case 'multicastLimit': case 'routes': case 'tags': case 'rules': case 'enableBroadcast': case 'mtu': case 'name': case 'private': case 'v4AssignMode': case 'v6AssignMode': this[k] = obj[k]; break; case 'activeMemberCount': case 'authorizedMemberCount': case 'totalMemberCount': case 'clock': case 'creationTime': case 'revision': this['__'+k] = parseInt(obj[k])||0; break; } } catch (e) {} } } } }; exports.Network = Network; class Member { constructor(obj) { this.clear(); this.patch(obj); } get objtype() { return 'member'; } get id() { return this._id; } set id(x) { this._id = formatZeroTierIdentifier((typeof x === 'number') ? x.toString(16) : x,10); } get address() { return this._id; } // legacy get nwid() { return this._nwid; } set nwid(x) { this._nwid = formatZeroTierIdentifier(x,16); } get controllerId() { return this.nwid.substr(0,10); } get authorized() { return this._authorized; } set authorized(b) { this._authorized = (b === true); } // security critical so require explicit set to true get activeBridge() { return this._activeBridge; } set activeBridge(b) { this._activeBridge = !!b; } get capabilities() { return this._capabilities; } set capabilities(c) { let caps = {}; let ca = []; if ((c)&&(Array.isArray(c))) { for(let a=0;a= 0)&&(capId <= 0xffffffff)&&(!caps[capId])) { caps[capId] = true; ca.push(capId); } } } ca.sort(); this._capabilities = ca; return ca; } get identity() { return this._identity; } set identity(istr) { if ((istr)&&(typeof istr === 'string')) this._identity = istr; else this._identity = null; } get ipAssignments() { return this._ipAssignments; } set ipAssignments(ipa) { let ips = {}; if ((ipa)&&(Array.isArray(ipa))) { for(let a=0;a 0) ips[ip] = true; } } this._ipAssignments = Object.keys(ips); this._ipAssignments.sort(); return this._ipAssignments; } get noAutoAssignIps() { return this._noAutoAssignIps; } set noAutoAssignIps(b) { this._noAutoAssignIps = !!b; } get tags() { return this._tags; } set tags(t) { let ta = []; let pairs = {}; if ((t)&&(Array.isArray(t))) { for(let a=0;a= 0)&&(tagId <= 0xffffffff)&&(tagValue >= 0)&&(tagValue <= 0xffffffff)&&(!pairs[pk])) { pairs[pk] = true; ta.push([ tagId,tagValue ]); } } } } ta.sort(function(a,b) { return ((a[0] < b[0]) ? -1 : ((a[0] > b[0]) ? 1 : 0)); }); this._tags = ta; return ta; } get creationTime() { return this.__creationTime; } get lastAuthorizedTime() { return this.__lastAuthorizedTime; } get lastAuthorizedCredentialType() { return this.__lastAuthorizedCredentialType; } get lastAuthorizedCredential() { return this.__lastAuthorizedCredential; } get lastDeauthorizedTime() { return this.__lastDeauthorizedTime; } get physicalAddr() { return this.__physicalAddr; } get revision() { return this.__revision; } get vMajor() { return this.__vMajor; } get vMinor() { return this.__vMinor; } get vRev() { return this.__vRev; } get vProto() { return this.__vProto; } toJSONExcludeControllerGenerated() { return { id: this.id, nwid: this.nwid, objtype: 'member', address: this.id, authorized: this.authorized, activeBridge: this.activeBridge, capabilities: this.capabilities, identity: this.identity, ipAssignments: this.ipAssignments, noAutoAssignIps: this.noAutoAssignIps, tags: this.tags }; } toJSON() { let j = this.toJSONExcludeControllerGenerated(); j.creationTime = this.creationTime; j.lastAuthorizedTime = this.lastAuthorizedTime; j.lastAuthorizedCredentialType = this.lastAuthorizedCredentialType; j.lastAuthorizedCredential = this.lastAuthorizedCredential; j.lastDeauthorizedTime = this.lastDeauthorizedTime; j.physicalAddr = this.physicalAddr; j.revision = this.revision; j.vMajor = this.vMajor; j.vMinor = this.vMinor; j.vRev = this.vRev; j.vProto = this.vProto; return j; } clear() { this._id = ''; this._nwid = ''; this._authorized = false; this._activeBridge = false; this._capabilities = []; this._identity = ''; this._ipAssignments = []; this._noAutoAssignIps = false; this._tags = []; this.__creationTime = 0; this.__lastAuthorizedTime = 0; this.__lastAuthorizedCredentialType = null; this.__lastAuthorizedCredential = null; this.__lastDeauthorizedTime = 0; this.__physicalAddr = ''; this.__revision = 0; this.__vMajor = 0; this.__vMinor = 0; this.__vRev = 0; this.__vProto = 0; } patch(obj) { if (obj instanceof Member) obj = obj.toJSON(); if ((obj)&&(typeof obj === 'object')&&(!Array.isArray(obj))) { for(var k in obj) { try { switch(k) { case 'id': case 'nwid': case 'authorized': case 'activeBridge': case 'capabilities': case 'identity': case 'ipAssignments': case 'noAutoAssignIps': case 'tags': this[k] = obj[k]; break; case 'creationTime': case 'lastAuthorizedTime': case 'lastAuthorizedCredentialType': case 'lastAuthorizedCredential': case 'lastDeauthorizedTime': case 'physicalAddr': case 'revision': case 'vMajor': case 'vMinor': case 'vRev': case 'vProto': this['__'+k] = parseInt(obj[k])||0; break; } } catch (e) {} } } } }; exports.Member = Member;