/**
** ==============================
** 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;
}
};