00001 #!node
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013 var cluster = require('cluster');
00014 var numCPUs = require("os").cpus().length;
00015 var fs = require('fs');
00016 var path_module = require('path');
00017 var module_holder = {};
00018 var workerData = {
00019 "serverbase": {}
00020 };
00021 var child_process = require('child_process');
00022
00023 var util = require('util');
00024
00025 var data_dir = "/tmp/artdaq_node_server";
00026 if (process.env.ARTDAQ_NODE_SERVER_DATADIR) {
00027 data_dir = process.env["ARTDAQ_NODE_SERVER_DATADIR"];
00028 } else if (!fs.existsSync(data_dir)) {
00029 fs.mkdirSync(data_dir);
00030 }
00031
00032
00033 var log_file = fs.createWriteStream(path_module.join(data_dir, 'server.' + process.env["USER"] + '.log'), { flags: 'a' });
00034 var log_stdout = process.stdout;
00035
00036 var getversion = function () {
00037 console.log("Getting Server Version");
00038 if (fs.existsSync("./version.txt")) {
00039 console.log("Reading Server Version from File");
00040 return "" + fs.readFileSync("./version.txt");
00041 }
00042 else if (fs.existsSync(path_module.join(data_dir, "version.txt"))) {
00043 console.log("Reading Server Version from File");
00044 return "" + fs.readFileSync(path_module.join(data_dir, "version.txt"));
00045 }
00046 else {
00047 child_process.exec("git describe --tags", function (error, stdout, stderr) {
00048 version = stdout.trim() + "-Git";
00049 child_process.exec("git status --porcelain", function (error, stdout) {
00050 if (stdout.length > 0) {
00051 version += "*";
00052 }
00053 fs.writeFileSync(path_module.join(data_dir, "version.txt"), version);
00054 });
00055 });
00056 }
00057 }
00058 var version = getversion();
00059
00060 var config = {
00061 ignored_modules: [],
00062 baseport: 8080,
00063 portOffset: 80,
00064 hostname: "localhost",
00065 module_config: []
00066 };
00067 function loadConfig() {
00068 if (fs.existsSync("config.json")) {
00069 config = JSON.parse(fs.readFileSync("config.json"));
00070 } else {
00071 fs.writeFileSync("config.json", JSON.stringify(config));
00072 }
00073
00074 if (process.env.ARTDAQDEMO_BASE_PORT) {
00075 config.baseport = parseInt(process.env["ARTDAQDEMO_BASE_PORT"]) + config.portOffset;
00076 }
00077
00078 if (config.hostname === "localhost" && cluster.isMaster) {
00079 console.log("Listening only on localhost. To listen on a different address, set \"hostname\" in config.json.\nUse \"0.0.0.0\" to listen on all interfaces.");
00080 }
00081 }
00082
00083 loadConfig();
00084
00085 console.log = function (d) {
00086 log_file.write(util.format(d) + '\n');
00087 log_stdout.write(util.format(d) + '\n');
00088 };
00089
00090 function MakePathToFile(filename) {
00091 var arr = filename.split(path_module.sep).slice(0, -1);
00092 if (fs.existsSync(path_module.join(arr))) { return; }
00093 var outputPath = data_dir;
00094 while (arr.length > 0) {
00095 outputPath = path_module.join(outputPath, arr.shift());
00096 if (!fs.existsSync(outputPath)) {
00097 fs.mkdirSync(outputPath);
00098 }
00099 }
00100 }
00101
00102 function LoadCerts(path) {
00103 if (!fs.existsSync(path)) {
00104 console.log("Creating certificates directory");
00105 fs.mkdirSync(path);
00106 var cert = fs.createWriteStream(path_module.join(path, "cilogon-basic.pem"));
00107 https.get("https://cilogon.org/cilogon-basic.pem", function (res) { res.pipe(cert); });
00108 var cert2 = fs.createWriteStream(path_module.join(path, "cilogon-basic.crt"));
00109 https.get("https://cilogon.org/cilogon-basic.crt", function (res) { res.pipe(cert2); });
00110 }
00111 var output = [];
00112 var files = fs.readdirSync(path);
00113 for (var i = 0; i < files.length; i++) {
00114 if (files[i].search(".pem") > 0 || files[i].search(".crt") > 0) {
00115 output.push(fs.readFileSync(path + "/" + files[i]));
00116 }
00117 }
00118 return output;
00119 }
00120
00121 function GetCILogonCRL(path) {
00122
00123 var file = fs.createWriteStream(path_module.join(path, "cilogon-basic.r0"));
00124 http.get("http://crl-cilogon.ncsa-security.net/cilogon-basic.r0", function (res) { res.pipe(file); });
00125 var file2 = fs.createWriteStream(path_module.join(path, "cilogon-basic.crl"));
00126 http.get("http://crl-cilogon.ncsa-security.net/cilogon-basic.crl", function (res) { res.pipe(file2); });
00127 }
00128
00129 function LoadCRLs(path) {
00130 if (!fs.existsSync(path)) {
00131 console.log("Creating directory " + path);
00132 fs.mkdirSync(path);
00133 }
00134 GetCILogonCRL(path);
00135 var output = [];
00136 var files = fs.readdirSync(path);
00137 for (var i = 0; i < files.length; i++) {
00138 if (files[i].search(".r0") > 0 || files[i].search(".crl") > 0) {
00139 output.push(fs.readFileSync(path + "/" + files[i]));
00140 }
00141 }
00142 return output;
00143 }
00144
00145
00146
00147 function LoadModules(path) {
00148 var stat = fs.lstatSync(path);
00149 if (stat.isDirectory()) {
00150
00151 var files = fs.readdirSync(path);
00152 var f, l = files.length;
00153 for (var i = 0; i < l; i++) {
00154 f = path_module.join(path, files[i]);
00155 LoadModules(f);
00156 }
00157 } else if (path.search("_module.js") > 0 && path.search("js~") < 0) {
00158 for (var im in config.ignored_modules) {
00159 if (config.ignored_modules.hasOwnProperty(im) && path.search(config.ignored_modules[im]) >= 0) {
00160 return;
00161 }
00162 }
00163 console.log("Loading Submodule " + path);
00164
00165
00166 require(path)(module_holder);
00167 console.log("Initialized Submodule " + path);
00168 }
00169 }
00170 var DIR = path_module.join(__dirname, "modules");
00171 LoadModules(DIR);
00172
00173
00174
00175 if (cluster.isMaster) {
00176
00177 function messageHandler(msg) {
00178
00179 if (!msg["name"]) {
00180
00181 console.log("Depreciated message recieved!");
00182 }
00183 if (msg["name"]) {
00184 if (msg["name"] === "request") {
00185 console.log("Request for Worker Data received");
00186 Object.keys(cluster.workers).forEach(function (id) {
00187 cluster.workers[id].send(workerData);
00188 });
00189 }
00190 else if (!msg["target"]) {
00191 console.log("Depreciated message recieved!");
00192
00193
00194
00195
00196
00197 } else {
00198 if (!msg["method"]) {
00199
00200 workerData[msg.name][msg.target] = msg.data;
00201 }
00202 else if (msg["method"] === "push") {
00203
00204 workerData[msg.name][msg.target].push(msg.data);
00205 }
00206 Object.keys(cluster.workers).forEach(function (id) {
00207 cluster.workers[id].send({ name: msg.name, target: msg.target, data: workerData[msg.name][msg.target] });
00208 });
00209 }
00210 }
00211 }
00212
00213
00214 for (var name in module_holder) {
00215 if (module_holder.hasOwnProperty(name)) {
00216 try {
00217 module_holder[name].MasterInitFunction(workerData, config.module_config[name]);
00218 } catch (err) {
00219 ;
00220 }
00221 module_holder[name].on("message", messageHandler);
00222 }
00223 }
00224
00225
00226 cluster.on('online', function (worker) {
00227 worker.send(workerData);
00228 });
00229
00230
00231 for (var i = 0; i < numCPUs; i++) {
00232
00233 var worker = cluster.fork();
00234 worker.on('message', messageHandler);
00235 }
00236
00237
00238 cluster.on("exit", function () {
00239 var newWorker = cluster.fork();
00240 newWorker.on('message', messageHandler);
00241 });
00242 } else {
00243
00244 var https = require('https');
00245 var http = require('http');
00246 var zlib = require('zlib');
00247 var url = require('url');
00248 var qs = require('querystring');
00249
00250 function workerMessageHandler(msg) {
00251 if (!msg["name"]) {
00252
00253
00254 workerData = msg;
00255 for (var name in module_holder) {
00256 if (module_holder.hasOwnProperty(name)) {
00257 try {
00258 module_holder[name].Update(workerData[name]);
00259 } catch (err) {
00260 ;
00261 }
00262 }
00263 }
00264 } else {
00265 if (!msg["target"]) {
00266
00267 workerData[msg.name] = msg.data;
00268 } else {
00269
00270 workerData[msg.name][msg.target] = msg.data;
00271 }
00272 try {
00273 module_holder[msg.name].Update(workerData[msg.name]);
00274 } catch (err) { ; }
00275 }
00276 }
00277
00278 process.send({ name: 'request' });
00279 process.on('message', workerMessageHandler);
00280
00281 for (var name in module_holder) {
00282 if (module_holder.hasOwnProperty(name)) {
00283 module_holder[name].on("message", function (data) {
00284
00285
00286 process.send(data);
00287 });
00288 try {
00289 module_holder[name].WorkerInitFunction(workerData);
00290 } catch (err) {
00291 ;
00292 }
00293 }
00294 }
00295
00296 function serve(req, res, readOnly, username) {
00297
00298
00299
00300
00301 var pathname = url.parse(req.url, true).pathname;
00302 if (pathname[0] === '/') {
00303 pathname = pathname.substr(1);
00304 }
00305
00306 var moduleName = pathname.substr(0, pathname.indexOf('/'));
00307 var functionName = pathname.substr(pathname.indexOf('/') + 1);
00308 if (workerData["serverbase"][req.connection.remoteAddress]) {
00309 var clientInfo = workerData["serverbase"][req.connection.remoteAddress];
00310 if (clientInfo.lastModuleName === moduleName && clientInfo.lastFunctionName === functionName) {
00311 if (clientInfo.lastReqTime >= Date.now() - 500) {
00312 console.log("Flood control active, denying request!");
00313 res.writeHeader(200, { 'Content-Type': 'text/html' });
00314 res.end("{\"Success\": true}");
00315 return;
00316 }
00317 }
00318 }
00319 process.send({
00320 name: "serverbase", target: req.connection.remoteAddress, data: {
00321 lastModuleName: moduleName,
00322 lastFunctionName: functionName,
00323 lastReqTime: Date.now()
00324 }
00325 });
00326
00327 var dnsDone = false;
00328
00329 require('dns').reverse(req.connection.remoteAddress, function (err, domains) {
00330 dnsDone = true;
00331 if (!err) {
00332 if (functionName.search(".min.map") < 0) {
00333
00334 console.log("PID: " + process.pid + ": " + "Received " + req.method + ", Client: " + domains[0] + " [" + req.connection.remoteAddress + "], Module: " + moduleName + ", function: " + functionName);
00335
00336 }
00337 return domains[0];
00338 } else {
00339 if (functionName.search(".min.map") < 0) {
00340
00341 console.log("Received " + req.method + ", Client: " + req.connection.remoteAddress + ", PID: " + process.pid + " Module: " + moduleName + ", function: " + functionName);
00342
00343 }
00344 return "";
00345 }
00346 });
00347 if (functionName.search("GET_ServerVersion") >= 0) {
00348 res.setHeader("Content-Type", "text/plain");
00349 res.statusCode = 200;
00350 res.end(version);
00351 return;
00352 }
00353 if (moduleName === ".." || functionName.search("\\.\\.") >= 0) {
00354 console.log("Possible break-in attempt!: " + pathname);
00355 res.writeHeader(404, { 'Content-Type': 'text/html' });
00356 res.end("Error");
00357 return;
00358 }
00359 res.setHeader("Content-Type", "application/json");
00360 res.statusCode = 200;
00361
00362
00363
00364
00365
00366
00367 if (req.method === "POST") {
00368 var body = "";
00369
00370
00371 req.on('data', function (data) {
00372 body += data;
00373 });
00374
00375 req.on('end', function () {
00376
00377 var post;
00378 try {
00379 post = JSON.parse(body);
00380 } catch (e) {
00381 post = qs.parse(body);
00382 }
00383 post.who = username;
00384
00385 if (module_holder[moduleName] != null) {
00386 console.log("Module " + moduleName + ", function " + functionName + " accessType " + (readOnly ? "RO" : "RW"));
00387 var dataTemp = "";
00388 module_holder[moduleName].removeAllListeners('data').on('data', function (data) {
00389 dataTemp += data;
00390 });
00391 module_holder[moduleName].removeAllListeners('end').on('end', function (data) {
00392
00393 res.end(JSON.stringify(dataTemp + data));
00394 });
00395 module_holder[moduleName].removeAllListeners('stream').on('stream', function (str, hdrs, code) {
00396 console.log("Stream message received: " + hdrs + " CODE: " + code);
00397 res.writeHead(code, hdrs);
00398 str.pipe(res);
00399 });
00400 var data;
00401 if (readOnly) {
00402 try {
00403 data = module_holder[moduleName]["RO_" + functionName](post, workerData[moduleName]);
00404 if (data != null) {
00405
00406 res.end(JSON.stringify(data));
00407 }
00408 } catch (err) {
00409 if (err instanceof TypeError) {
00410
00411 res.end(JSON.stringify(null));
00412 }
00413 }
00414 } else {
00415 try {
00416 data = module_holder[moduleName]["RW_" + functionName](post, workerData[moduleName]);
00417 if (data != null) {
00418
00419 res.end(JSON.stringify(data));
00420 }
00421 } catch (err2) {
00422 console.log("Error caught; text: " + JSON.stringify(err2));
00423 if (err2 instanceof TypeError) {
00424
00425 data = module_holder[moduleName]["RO_" + functionName](post, workerData[moduleName]);
00426 if (data != null) {
00427
00428 res.end(JSON.stringify(data));
00429 }
00430 }
00431 }
00432 }
00433 } else {
00434 console.log("Unknown POST URL: " + pathname);
00435 res.writeHeader(404, { 'Content-Type': 'text/html' });
00436 res.end("Error");
00437 }
00438 });
00439 }
00440
00441 if (req.method === "GET" || req.method === "HEAD") {
00442
00443 if (functionName.indexOf(".") > 0) {
00444
00445 var ext = functionName.substr(functionName.lastIndexOf(".") + 1);
00446 res.setHeader("Content-Type", "text/plain");
00447
00448 switch (ext) {
00449 case "css":
00450 res.setHeader("Content-Type", "text/css");
00451 break;
00452 case "js":
00453 res.setHeader("Content-Type", "text/javascript");
00454 break;
00455 case "html":
00456 res.setHeader("Content-Type", "text/html");
00457 break;
00458 case "htm":
00459 res.setHeader("Content-Type", "text/html");
00460 break;
00461 case "root":
00462 res.setHeader("Content-Type", "application/root+root.exe");
00463 break;
00464 case "gif":
00465 res.setHeader("Content-Type", "image/gif");
00466 break;
00467 }
00468
00469 var filename = "./modules/" + moduleName + "/client/" + functionName;
00470 if (functionName.search("favicon.ico") >= 0) {
00471 filename = "./modules/base/client/images/favicon.ico";
00472 }
00473 if (fs.existsSync(filename)) {
00474 res.setHeader("Content-Length", fs.statSync(filename)["size"]);
00475 if (req.headers.range != null) {
00476 var range = req.headers.range;
00477 var offset = parseInt(range.substr(range.indexOf('=') + 1, range.indexOf('-') - (range.indexOf('=') + 1)));
00478 var endOffset = parseInt(range.substr(range.indexOf('-') + 1));
00479 console.log("Reading (" + offset + ", " + endOffset + ")");
00480
00481 res.setHeader("Content-Length", (endOffset - offset + 1).toString());
00482 var readStream = fs.createReadStream(filename, { start: parseInt(offset), end: parseInt(endOffset) });
00483 readStream.pipe(res);
00484 } else {
00485 console.log("PID: " + process.pid + ": " + "Sending file");
00486 res.end(fs.readFileSync(filename));
00487 }
00488 console.log("PID: " + process.pid + ": " + "Done sending file " + filename);
00489 } else {
00490 console.log("File not found: " + filename);
00491 res.setHeader("Content-Type", "text/plain");
00492 res.end("File Not Found.");
00493 }
00494 } else if (module_holder[moduleName] != null) {
00495
00496
00497 var dataTemp = "";
00498 module_holder[moduleName].removeAllListeners('data').on('data', function (data) {
00499
00500 dataTemp += data;
00501 });
00502 module_holder[moduleName].removeAllListeners('end').on('end', function (data) {
00503
00504 res.end(JSON.stringify(dataTemp + data));
00505 });
00506 module_holder[moduleName].removeAllListeners('stream').on('stream', function (str, hdrs, code) {
00507 res.writeHead(code, hdrs);
00508 str.pipe(res);
00509 });
00510 var data = module_holder[moduleName]["GET_" + functionName](workerData[moduleName]);
00511 if (data != null) {
00512
00513 res.end(JSON.stringify(data));
00514 }
00515 } else {
00516 console.log("Sending client.html");
00517
00518 res.setHeader("Content-Type", "text/html");
00519 res.end(fs.readFileSync("./client.html"), 'utf-8');
00520 console.log("Done sending client.html");
00521 }
00522 }
00523 };
00524
00525 console.log("Setting up options");
00526 var options = {
00527 key: fs.readFileSync('./certs/server.key'),
00528 cert: fs.readFileSync('./certs/server.crt'),
00529 ca: LoadCerts(path_module.join(data_dir, "certificates")),
00530 crl: LoadCRLs(path_module.join(data_dir, "certificates")),
00531 requestCert: true,
00532 rejectUnauthorized: false
00533 };
00534 var authlist = " " + fs.readFileSync("./certs/authorized_users");
00535 console.log("Done setting up options");
00536
00537
00538 var server = https.createServer(options, function (req, res) {
00539 var readOnly = true;
00540 var clientCertificate = req.connection.getPeerCertificate();
00541 var username = "HTTPS User";
00542 if (req.client.authorized) {
00543
00544
00545
00546
00547
00548 username = clientCertificate.subject.CN[0];
00549 var useremail = clientCertificate.subjectaltname.substr(6);
00550 if (authlist.search(username) > 0 || authlist.search(useremail) > 0) {
00551 readOnly = false;
00552 }
00553 }
00554
00555 console.log("User: " + username + ", readOnly: " + readOnly);
00556
00557 try {
00558 serve(req, res, readOnly, username);
00559 } catch (e) {
00560 console.trace("Unhandled error in serve: " + JSON.stringify(e));
00561 }
00562 });
00563 var insecureServer = http.createServer(function (req, res) {
00564
00565 try {
00566 serve(req, res, false, "HTTP User");
00567 } catch (e) {
00568 console.trace("Unhandled error in serve: " + JSON.stringify(e));
00569 }
00570 });
00571
00572 console.log("Listening on " + config.hostname + " ports " + config.baseport + " and " + (config.baseport + 1));
00573 server.listen(config.baseport + 1, config.hostname);
00574 insecureServer.listen(config.baseport, config.hostname);
00575 }