diff --git a/js/dos/dns_srv_emb.js b/js/dos/dns_srv_emb.js new file mode 100644 index 0000000..61be1ab --- /dev/null +++ b/js/dos/dns_srv_emb.js @@ -0,0 +1,618 @@ +/** + ** ============================== + ** 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; + } +};