1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/lib/app.js Wed Aug 13 21:02:03 2014 +0200 1.3 @@ -0,0 +1,212 @@ 1.4 +#! /usr/bin/env nodejs 1.5 +// 1.6 +// mDNSGw - Zero Configuration DNS Gateway for Mesh Networks 1.7 +// Copyright © 2014 Michael Schloh von Bennewitz <michael@schloh.com> 1.8 +// 1.9 +// Permission to use, copy, modify, and/or distribute this software for 1.10 +// any purpose with or without fee is hereby granted, provided that the 1.11 +// above copyright notice and this permission notice appear in all copies. 1.12 +// 1.13 +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 1.14 +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 1.15 +// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 1.16 +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 1.17 +// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 1.18 +// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 1.19 +// ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 1.20 +// THIS SOFTWARE. 1.21 +// 1.22 +// This file is part of mDNSGw, a Zero configuration DNS gateway 1.23 +// which can be found at http://dev.europalab.com/mdnsgw/ 1.24 +// 1.25 +// app.js: ECMA JavaScript implementation 1.26 +// 1.27 + 1.28 +/*********************************************************** 1.29 +| ____ _ _ ____ ____ | 1.30 +| _ __ ___ | _ \| \ | / ___| / ___|_ __ | 1.31 +| | '_ ` _ \| | | | \| \___ \| | _\ \ /\ / / | 1.32 +| | | | | | | |_| | |\ |___) | |_| |\ V V / | 1.33 +| |_| |_| |_|____/|_| \_|____/ \____| \_/\_/ | 1.34 +| | 1.35 +| Requirements: Redis server with standard configuration | 1.36 +| NodeJS and NPM modules (see package.json) | 1.37 +| | 1.38 +| Execute: To start this application, launch it with the | 1.39 +| script named fork.js: $ ./fork.js | 1.40 +| | 1.41 +| Support: http://list.europalab.com/mailman/mdnsgs/ | 1.42 +| | 1.43 +***********************************************************/ 1.44 + 1.45 +// import module dependencies 1.46 +var mdnsinst = require('mdns'); 1.47 +var redisdat = require('redis'); 1.48 +var nameinst = require('native-dns'); 1.49 + 1.50 + 1.51 +// install POSIX signal handlers 1.52 +process.on('SIGUSR2', function() { 1.53 + console.log('SIGUSR2: Dumping mDNSGw entries at', Date()); 1.54 + rediscli.hgetall('hostnames', function (error, object) {console.dir(object);}); 1.55 +}); 1.56 +process.on('SIGHUP', function() { 1.57 + console.log('SIGHUP: Cleared all database entries at', Date()); 1.58 + cleardb(); 1.59 +}); 1.60 + 1.61 +// instantiate a new redis client 1.62 +// http://www.rediscookbook.org/ 1.63 +var rediscli = redisdat.createClient(); 1.64 +rediscli.on('error', function (error) { 1.65 + console.log('Error ' + error); 1.66 +}); 1.67 + 1.68 +// clear mDNS service keys 1.69 +function cleardb () { 1.70 + rediscli.del('hostnames'); 1.71 + // this is not working unfortunately for the loop 1.72 + rediscli.keys('*', function (error, replies) { 1.73 + replies.forEach(function (reply, ident) { 1.74 + rediscli.del(reply, function (error, value) { 1.75 + if (error) throw(error); 1.76 + }); 1.77 + }); 1.78 + }); 1.79 +} 1.80 + 1.81 +// scan all advertised mDNS service types 1.82 +cleardb(); // clear old data first 1.83 +var browsall = mdnsinst.browseThemAll(); 1.84 +browsall.on('serviceUp', function(service) { 1.85 + // iterate through hosts and watch accordingly 1.86 + if (service.type.name.match(/^[a-zA-Z0-9\-]+$/)) { // mdns module hack 1.87 + if (service.type.protocol == 'tcp') { 1.88 + var browserv = mdnsinst.createBrowser(mdnsinst.tcp(service.type.name)); 1.89 + } 1.90 + else if (service.type.protocol == 'udp') { 1.91 + var browserv = mdnsinst.createBrowser(mdnsinst.udp(service.type.name)); 1.92 + } 1.93 + else if (service.type.protocol == 'sctp') { 1.94 + var browserv = mdnsinst.createBrowser(mdnsinst.sctp(service.type.name)); 1.95 + } 1.96 + else throw(error); 1.97 + 1.98 + // common logic for all transports (TCP, UDP, SCTP, etcetera) 1.99 + browserv.on('serviceUp', function(service) { 1.100 + //console.log('service up: ', service); 1.101 + //{interfaceIndex: 2, type: {name: 'ssh', protocol: 'tcp', subtypes: [], fullyQualified: true}, replyDomain: 'local.', flags: 2, name: 'hostname-mich', networkInterface: 'eth0', fullname: 'hostname-mich._ssh._tcp.local.', host: 'hostname-mich.local.', port: 22, addresses: ['192.168.1.50']} 1.102 + 1.103 + // insert one or more IP addresses for each hostname.local. 1.104 + rediscli.hset('hostnames', service.host.replace(/\.$/, ''), service.addresses); 1.105 + }); 1.106 + browserv.on('serviceDown', function(service) { 1.107 + console.log('service down: ', service); 1.108 + //FIXME: still need to selectively remove hosts 1.109 + }); 1.110 + browserv.on('serviceChanged', function(service) { 1.111 + console.log('service changed: ', service); 1.112 + //FIXME: still need to selectively update hosts 1.113 + }); 1.114 + browserv.start(); 1.115 + } 1.116 +}); 1.117 +browsall.start(); 1.118 + 1.119 +// instantiate a new DNS server 1.120 +var nameserv = nameinst.createServer(); 1.121 + 1.122 +nameserv.on('request', function (request, response) { 1.123 + //console.log(request) 1.124 + 1.125 + // ensure that requested hostname is present 1.126 + rediscli.hget('hostnames', request.question[0].name, function (error, value) { 1.127 + // handle unexpected errors 1.128 + if (error) throw(error); 1.129 + 1.130 + if (value) { // the db succeeded in finding a match 1.131 + // populate the DNS response with the chosen hostname 1.132 + rediscli.hkeys('hostnames', function (error, replies) { 1.133 + replies.forEach(function (host, index) { 1.134 + if (request.question[0].name == host) { 1.135 + rediscli.hget('hostnames', host, function (error, value) { 1.136 + // handle unexpected errors 1.137 + if (error) throw(error); 1.138 + 1.139 + // FIXME: still must handle multihomed hosts 1.140 + //// a host might have more than one address 1.141 + //value.forEach(function (addr, iter) 1.142 + // set the nameserver address 1.143 + response.answer.push(nameinst.A({ 1.144 + name: host, 1.145 + address: value, 1.146 + ttl: 600, 1.147 + })); 1.148 + response.send(); 1.149 + }); 1.150 + } 1.151 + }); 1.152 + }); 1.153 + } 1.154 + else { 1.155 + response.answer.push(nameinst.A({ 1.156 + name: request.question[0].name, 1.157 + address: '127.0.0.1', 1.158 + ttl: 600, 1.159 + })); 1.160 + response.send(); 1.161 + } 1.162 + }); 1.163 +}); 1.164 + 1.165 +// DNS error handler logic 1.166 +nameserv.on('error', function (err, buff, req, res) { 1.167 + console.log('DNS problem: ', err.stack); 1.168 +}); 1.169 + 1.170 +//// debug process user 1.171 +//console.log('Start.'); 1.172 +//console.log(process.env.USER); 1.173 +//console.log(process.env.SUDO_USER); 1.174 +//console.log('Done.'); 1.175 +// 1.176 +// <1024 must run privileged 1.177 +var nudpport = 53; // default DNS 1.178 +if (nudpport < 1024 && process.getuid() !== 0) { 1.179 + //console.log('Serving on port <1024 from an unprivileged user.\nChange to root if using a privileged port number.') 1.180 + throw new Error('Serving on port <1024 from an unprivileged user.') 1.181 +} 1.182 + 1.183 +// start the DNS process 1.184 +nameserv.serve(nudpport, function () { 1.185 + try { 1.186 + console.log('Starting mDNSGw on', Date()); 1.187 + process.stdout.write('Old UID: ' + process.getuid() + ', Old GID: ' + process.getgid() + '... '); 1.188 + process.umask('0644'); 1.189 + process.setgid('daemon'); 1.190 + if (process.env.SUDO_USER) 1.191 + process.setuid(process.env.SUDO_USER); 1.192 + else 1.193 + process.setuid('daemon'); 1.194 + console.log('New UID: ' + process.getuid() + ', New GID: ' + process.getgid()); 1.195 + } catch (err) { 1.196 + console.log('Cowardly refusing to keep the process alive as root.'); 1.197 + process.exit(1); 1.198 + } 1.199 +}); 1.200 + 1.201 +//// debug print all key and value database entries 1.202 +//rediscli.hgetall('hostnames', function (error, object) {console.dir(object);}); 1.203 + 1.204 +//// display stored mDNS service data entries 1.205 +//rediscli.hkeys('hostnames', function (error, replies) { 1.206 +// console.log(replies.length + ' replies:'); 1.207 +// replies.forEach(function (reply, ident) { 1.208 +// console.log(' ' + ident + ': ' + reply); 1.209 +// }); 1.210 +//}); 1.211 + 1.212 +//// block executes on program termination 1.213 +//rediscli.quit(); // cleanup db connection 1.214 +//browserv.stop(); // zombie scope too bad 1.215 +//browsall.stop();