/** * Notification server. Launch with: * * sudo node aphlict_server.js --user=aphlict * * You can also specify `port`, `admin`, `host` and `log`. */ var config = parse_command_line_arguments(process.argv); function parse_command_line_arguments(argv) { var config = { port : 22280, admin : 22281, host : '127.0.0.1', user : null, log: '/var/log/aphlict.log' }; for (var ii = 2; ii < argv.length; ii++) { var arg = argv[ii]; var matches = arg.match(/^--([^=]+)=(.*)$/); if (!matches) { throw new Error("Unknown argument '"+arg+"'!"); } if (!(matches[1] in config)) { throw new Error("Unknown argument '"+matches[1]+"'!"); } config[matches[1]] = matches[2]; } config.port = parseInt(config.port, 10); config.admin = parseInt(config.admin, 10); return config; } if (process.getuid() !== 0) { console.log( "ERROR: "+ "This server must be run as root because it needs to bind to privileged "+ "port 843 to start a Flash policy server. It will downgrade to run as a "+ "less-privileged user after binding if you pass a user in the command "+ "line arguments with '--user=alincoln'."); process.exit(1); } var net = require('net'); var http = require('http'); var url = require('url'); var querystring = require('querystring'); var fs = require('fs'); // set up log file var logfile = fs.createWriteStream( config.log, { flags: 'a', encoding: null, mode: 0666 }); function log(str) { console.log(str); logfile.write(str + '\n'); } process.on('uncaughtException', function (err) { log("\n<<< UNCAUGHT EXCEPTION! >>>\n\n" + err); process.exit(1); }); log('----- ' + (new Date()).toLocaleString() + ' -----\n'); function getFlashPolicy() { return [ '', '', '', '', '' ].join('\n'); } net.createServer(function(socket) { socket.write(getFlashPolicy() + '\0'); socket.end(); log('[' + socket.remoteAddress + '] Sent Flash Policy'); socket.on('error', function (e) { log('Error in policy server: ' + e); }); }).listen(843); function write_json(socket, data) { var serial = JSON.stringify(data); var length = Buffer.byteLength(serial, 'utf8'); length = length.toString(); while (length.length < 8) { length = '0' + length; } socket.write(length + serial); } var clients = {}; var current_connections = 0; // According to the internet up to 2^53 can // be stored in javascript, this is less than that var MAX_ID = 9007199254740991;//2^53 -1 // If we get one connections per millisecond this will // be fine as long as someone doesn't maintain a // connection for longer than 6854793 years. If // you want to write something pretty be my guest function generate_id() { if (typeof generate_id.current_id == 'undefined' || generate_id.current_id > MAX_ID) { generate_id.current_id = 0; } return generate_id.current_id++; } var send_server = net.createServer(function(socket) { var client_id = generate_id(); var client_name = '[' + socket.remoteAddress + '] [#' + client_id + '] '; socket.on('connect', function() { clients[client_id] = socket; current_connections++; log(client_name + 'connected\t\t(' + current_connections + ' current connections)'); }); socket.on('close', function() { delete clients[client_id]; current_connections--; log(client_name + 'closed\t\t(' + current_connections + ' current connections)'); }); socket.on('timeout', function() { log(client_name + 'timed out!'); }); socket.on('end', function() { log(client_name + 'ended the connection'); // node automatically closes half-open connections }); socket.on('error', function (e) { log(client_name + 'Uncaught error in send server: ' + e); }); }).listen(config.port); var messages_out = 0; var messages_in = 0; var start_time = new Date().getTime(); var receive_server = http.createServer(function(request, response) { response.writeHead(200, {'Content-Type' : 'text/plain'}); // Publishing a notification. if (request.method == 'POST') { var body = ''; request.on('data', function (data) { body += data; }); request.on('end', function () { ++messages_in; var data = querystring.parse(body); log('notification: ' + JSON.stringify(data)); broadcast(data); response.end(); }); } else if (request.url == '/status/') { request.on('data', function(data) { // We just ignore the request data, but newer versions of Node don't // get to 'end' if we don't process the data. See T2953. }); request.on('end', function() { var status = { 'uptime': (new Date().getTime() - start_time), 'clients.active': current_connections, 'clients.total': generate_id.current_id || 0, 'messages.in': messages_in, 'messages.out': messages_out, 'log': config.log }; response.write(JSON.stringify(status)); response.end(); }); } else { response.statusCode = 400; response.write('400 Bad Request'); response.end(); } }).listen(config.admin, config.host); function broadcast(data) { for (var client_id in clients) { try { write_json(clients[client_id], data); ++messages_out; log('wrote to client ' + client_id); } catch (error) { delete clients[client_id]; current_connections--; log('ERROR: could not write to client ' + client_id); } } } // If we're configured to drop permissions, get rid of them now that we've // bound to the ports we need and opened logfiles. if (config.user) { process.setuid(config.user); }