/** ** ============================== ** O O O OOOO ** O O O O O O ** O O O O O O ** OOOO OOOO O OOO OOOO ** O O O O O O O ** O O O O O O O ** OOOO OOOO O O OOOO ** ============================== ** BSSLAB, Dr. Stefan Bosse http://www.bsslab.de ** ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED ** BY THE AUTHOR(S). ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, ** MODIFIED, OR OTHERWISE USED IN A CONTEXT ** OUTSIDE OF THE SOFTWARE SYSTEM. ** ** $AUTHORS: Stefan Bosse ** $INITIAL: (C) 2006-2017 bLAB ** $CREATED: 2-6-15 by sbosse. ** $VERSION: 1.3.1 ** ** $INFO: ** ** DOS: DNS Server, Embedded version * * Simplified embeddable DNS server without external persistent storage, used, * for example, by the host server publishing servers. The DNS service can be * restricted to handle only one root directory (restricted root mode). ** ** $ENDOFINFO */ "use strict"; var log=0; var util = Require('util'); var Io = Require('com/io'); var trace = Io.tracing; var Net = Require('dos/network'); var Std = Require('dos/std'); var Sch = Require('dos/scheduler'); var Buf = Require('dos/buf'); var Rpc = Require('dos/rpc'); var Dns = Require('dos/dns_srv_common'); var Cs = Require('dos/capset'); var Comp = Require('com/compat'); var Cache = Require('dos/cache'); var Filename = Comp.filename; var String = Comp.string; var Array = Comp.array; var Perv = Comp.pervasives; var assert = Comp.assert; var div = Perv.div; var Rand = Comp.random; var Status = Net.Status; var Command = Net.Command; var Rights = Net.Rights; var Printf = Comp.printf; /** * DNS Embedded Server Class * @param {rpcint} rpc * @constructor * @typedef {{rpc,std,cs,pubport,privport,random,rootmode,rootcs:capset,rootdir:dns_dir,ncols,cols,dirs,dirs_free,dirs_top,stats}} dns_server_emb~obj * @see dns_server_emb~obj * @see dns_server_emb~meth */ var dns_server_emb = function (rpc) { this.rpc=rpc; /** @type {std} std */ this.std=Std.StdInt(rpc); this.cs=Cs.CsInt(rpc); this.pubport=undefined; this.privport=undefined; this.random=undefined; // one root directory only? this.rootmode=false; // root capability set this.rootcs=undefined; // root directory object this.rootdir=undefined; this.ncols=0; this.cols=[]; this.lock=Sch.Lock(); /** Capability cache to speed-up row * capability restriction using a column mask. * Key: cap-key+mask * Value: Restricted cap * */ this.cache_cap = Cache.Cache(100); /* ** Directory partition: associative array */ this.dirs=[]; this.dirs_free=[]; this.dirs_top=0; this.stats = { op_read:0, op_modify:0, op_create:0, op_destroy:0 } }; /** Initialite and start up services * */ dns_server_emb.prototype.init = function () { var self=this; Sch.AddTimer(1000,'DNS Server Garbage Collector',function () { self.cache_cap.refresh(function (key,data) { if (data.tmo) { data.tmo--; return data.tmo>0} else return true; }); }); } dns_server_emb.prototype.lock = function () { this.lock.acquire(); }; dns_server_emb.prototype.try_lock = function () { return this.lock.try_acquire(); }; dns_server_emb.prototype.unlock = function () { return this.lock.release(); }; /** * @typedef {{ * create_dns:dns_server.create_dns, * lookup_dir:dns_server.lookup_dir, * check_dir:dns_server.check_dir, * create_dir:dns_server.create_dir, * acquire_dir:dns_server.acquire_dir, * release_dir:dns_server.release_dir, * request_dir:dns_server.request_dir, * restrict:dns_server.restrict, * capset_of_dir:dns_server.capset_of_dir, * dir_of_capset:dns_server.dir_of_capset, * search_row:dns_server.search_row, * append_row:dns_server.append_row, * rename_row:dns_server.rename_row, * time:dns_server.time, * }} dns_server_emb~meth */ /** * * @param {port} pubport * @param {port} privport * @param {port} random * @param {boolean} rootmode * @param {string []} cols */ dns_server_emb.prototype.create_dns = function (pubport,privport,random,rootmode,cols) { this.pubport=pubport; this.privport=privport; this.random=random; this.ncols=cols.length; this.cols=cols; this.rootmode=rootmode; this.dirs=[]; this.dirs_free=[]; this.dirs_top=1; // The next not allocated entry /* ** Create the root directory (object number 1) */ this.rootdir=this.create_dir(cols); this.rootdir.dd_random=random; this.rootcs=this.capset_of_dir(this.rootdir,Rights.PRV_ALL_RIGHTS); this.release_dir(this.rootdir); }; /** Lookup a directory object (priv.prv_obj) and check the required rights. * The directory is returned unlocked! * * @param {privat} priv * @param {number} req * @returns {dns_dir|undefined} */ dns_server_emb.prototype.lookup_dir = function(priv,req) { var self = this; var obj = Net.prv_number(priv); if (obj < 1 || obj >= this.dirs_top) return undefined; var dir = self.dirs[obj]; var rights = Net.prv_rights(priv); /* ** When checking the presence of column rights, only take the ** *actual* ** columns present into account (i.e., do not use DNS_COLMASK here) */ var colmask = Dns.dns_col_bits[dir.dd_ncols] - 1; Io.log((log<1)||('Dns_server.lookup_dir: '+Net.Print.private(priv))+' req='+req+' colmask='+colmask+' rights='+rights); if (!Net.prv_decode(priv, dir.dd_random)) { return undefined; } if ((rights & colmask) == 0 || (rights & req) != req) { return undefined; } return dir; }; /** ** With a given directory capability set check if this directory belongs ** to this server. * * * @param {capset} dir * @returns {private|undefined} */ dns_server_emb.prototype.check_dir = function(dir) { var self=this; Io.log((log<1)||('Dns_server.check_dir: '+Cs.Print.capset(dir))); if (dir==undefined) return undefined; for (var i = 0; i= this.dirs_top) callback(Status.STD_OBJBAD,undefined); else { var dir = this.dirs[obj]; if (dir == undefined) callback(Status.STD_NOTFOUND,undefined); else Sch.ScheduleBlock([ function () {dir.lock();}, function () {callback(Status.STD_OK,dir)} ]); } }; /** Release an acquired directory * @param {dns_dir} dir * @param callback */ dns_server_emb.prototype.release_dir = function(dir,callback) { if (dir.dd_state == Dns.Dns_dir_state.DD_unlocked || dir.dd_state == Dns.Dns_dir_state.DD_modified) { // Nothing to do here! dir.dd_state=Dns.Dns_dir_state.DD_locked; } dir.unlock(); if (callback!=undefined) callback(Status.STD_OK); }; /** Release an unmodified directory [NON BLOCKING]. * * @param dir */ dns_server_emb.prototype.release_unmodified_dir = function(dir) { this.release_dir(dir); }; /** ** A client request arrived. Enter the critical section. Check the capability. ** If correct, check whether the required rights are present or not. ** Return the directory structure, but only if the required rights are present. * * @param {private} priv * @param req * @param {function((Status.STD_OK|*),dns_dir|undefined)} callback */ dns_server_emb.prototype.request_dir = function(priv,req,callback) { var self=this; var obj = Net.prv_number(priv); var dir=undefined; var stat=Status.STD_UNKNOWN; Sch.ScheduleBlock([ function () { Io.log((log<1)||('Dns_server.request_dir: acquire_dir('+obj+')')); self.acquire_dir(obj,function (_stat,_dir) { stat=_stat; dir=_dir; }) }, function () { if (stat!=Status.STD_OK) throw stat; var rights = Net.prv_rights(priv); /* ** When checking the presence of column rights, only take the ** *actual* ** columns present into account (i.e., do not use DNS_COLMASK here) */ var colmask = Dns.dns_col_bits[dir.dd_ncols]-1; Io.log((log<1)||('Dns_server.request_dir: '+Net.Print.private(priv))+' req='+req+' colmask='+colmask+' rights='+rights); if (!Net.prv_decode(priv,dir.dd_random)) { throw Status.STD_DENIED; } if ((rights & colmask) ==0 || (rights & req) != req) { throw Status.STD_DENIED; } callback(Status.STD_OK,dir); } ],function (e) { if (dir!=undefined) self.release_unmodified_dir(dir); if (typeof e != 'number') {Io.printstack(e,'Dns_server.request_dir');} if (typeof e == 'number') callback(e,undefined); else callback(Status.STD_SYSERR,undefined); }); }; /** Restrict a directory capability set. * The directoty structure from capability set 'dir' must be unlocked to avoid. * * @param {capset} cs * @param mask * @param {function((Status.STD_OK|*),capset|undefined)} callback */ dns_server_emb.prototype.restrict = function(cs,mask,callback) { var self=this, cs=Cs.Copy.capset(cs), stat=Status.STD_CAPBAD, index=0; Sch.ScheduleLoop(function() { return (stat!=Status.STD_OK && index= this.dirs_top) return undefined; else { dir = this.dirs[obj]; if (dir.dd_lock.is_locked()) return undefined; return dir; } }; /** Search a row in the directory with give name. * * @param {dns_dir} dir * @param {string} name * @returns {dns_row|undefined} */ dns_server_emb.prototype.search_row = function(dir,name) { return Array.find(dir.dd_rows,function (row) { return String.equal(row.dr_name,name); }) }; /** Append a row to a directory. * * @param {dns_dir} dir * @param {dns_row} row */ dns_server_emb.prototype.append_row = function(dir,row) { dir.dd_rows.push(row); dir.dd_nrows++; dir.dd_state=Dns.Dns_dir_state.DD_modified; }; /** Rename a row. Return status. * * @param {dns_dir} dir * @param oldname * @param newname * @returns {(Status.STD_OK|*)} */ dns_server_emb.prototype.rename_row = function(dir,oldname,newname) { var row=Array.find(dir.dd_rows,function(row) { return (String.equal(row.dr_name,oldname)); }); if (row!=undefined) { /* ** Check that the new entry name doesn't exist already */ var exist = Array.check(dir.dd_rows,function(row) { return (String.equal(row.dr_name,newname)); }); if (exist) return Status.STD_EXISTS; row.dr_name=newname; dir.dd_state=Dns.Dns_dir_state.DD_modified; return Status.STD_OK; } else return Status.STD_NOTFOUND; }; /** Search a row in the directory with give name. * * @param {dns_dir} dir * @param {string} name */ dns_server_emb.prototype.delete_row = function(dir,name) { var found; dir.dd_rows = Array.filter(dir.dd_rows,function (row) { var eq=String.equal(row.dr_name,name); if (eq) found=true; return !eq; }); dir.dd_nrows=Array.length(dir.dd_rows); if (found) return Status.STD_OK; else return Status.STD_NOTFOUND; }; dns_server_emb.prototype.stat = function () { var str='Statistics\n==========\n'; str=str+Printf.sprintf2([['%20s','#DIRS'],':',['%8d',this.dirs.length],' ', ['%20s','#FREE'],':',['%8d',this.dirs_free.length]])+'\n'; str=str+Printf.sprintf2([['%20s','READ'],':',['%8d',this.stats.op_read],' ', ['%20s','MODIFY'],':',['%8d',this.stats.op_modify]])+'\n'; str=str+Printf.sprintf2([['%20s','CREATE'],':',['%8d',this.stats.op_create],' ', ['%20s','DESTROY'],':',['%8d',this.stats.op_destroy]])+'\n'; return str; } /** ** Get the current system time in 10s units * * @returns {number} */ dns_server_emb.prototype.time =function () { var self=this; return div(Perv.time(),10); }; /* ** ================ ** PUBLIC INTERFACE ** ================ */ var DnsRpc = Require('dos/dns_srv_rpc'); //dns_server.prototype=new DnsRpc.dns_server_rpc(); dns_server_emb.prototype.dns_lookup=DnsRpc.dns_server_rpc.prototype.dns_lookup; dns_server_emb.prototype.dns_create=DnsRpc.dns_server_rpc.prototype.dns_create; dns_server_emb.prototype.dns_append=DnsRpc.dns_server_rpc.prototype.dns_append; dns_server_emb.prototype.dns_rename=DnsRpc.dns_server_rpc.prototype.dns_rename; dns_server_emb.prototype.dns_delete=DnsRpc.dns_server_rpc.prototype.dns_delete; dns_server_emb.prototype.dns_info=DnsRpc.dns_server_rpc.prototype.dns_info; dns_server_emb.prototype.dns_stat=DnsRpc.dns_server_rpc.prototype.dns_stat; dns_server_emb.prototype.dns_list=DnsRpc.dns_server_rpc.prototype.dns_list; dns_server_emb.prototype.dns_setlookup=DnsRpc.dns_server_rpc.prototype.dns_setlookup; module.exports = { /** * * @param {rpcint} rpc * @returns {dns_server_emb} */ Server: function(rpc) { var obj = new dns_server_emb(rpc); Object.preventExtensions(obj); return obj; } };