artdaq_node_server  v1_00_14
HTTPSizer.js
1 #!node
2 // serverbase.js : v0.5 : Node HTTPS Server
3 // Author: Eric Flumerfelt, FNAL RSI
4 // Last Modified: June 3, 2015
5 // Modified By: Eric Flumerfelt
6 //
7 // serverbase sets up a basic HTTPS server and directs requests
8 // to one of its submodules.
9 //
10 // Implementation Notes: modules should assign their emitter to the module_holder[<modulename>] object
11 // modules will emit 'data' and 'end' signals and implement the function MasterInitFunction()
12 
13 var cluster = require('cluster');
14 var numCPUs = require("os").cpus().length;
15 var fs = require('fs');
16 var path_module = require('path');
17 var child_process = require('child_process');
18 
19 var util = require('util');
20 
21 var getversion = function () {
22  //console.log("Getting Server Version");
23  if (fs.existsSync("./version.txt")) {
24  //console.log("Reading Server Version from File");
25  return "" + fs.readFileSync("./version.txt");
26  }
27  else {
28  child_process.exec("git describe --tags", function (error, stdout, stderr) {
29  version = stdout.trim() + "-Git";
30  child_process.exec("git status --porcelain", function (error, stdout) {
31  if (stdout.length > 0) {
32  version += "*";
33  }
34  });
35  });
36  }
37 }
38 var version = getversion();
39 
40 var config = {
41  listenhost: "localhost",
42  listenport: 8080,
43  xdaqhost: "localhost",
44  xdaqport: 2015,
45  xdaqmode: true,
46  logFileName: "/tmp/xdaqproxy." + process.env["USER"] + ".log",
47  certInfoFile: "/tmp/xdaqproxy." + process.env["USER"] + ".certdata",
48  logConsoleEnabled: true
49 };
50 function loadConfig() {
51  if (fs.existsSync("xdaq_config.json")) {
52  config = JSON.parse(fs.readFileSync("xdaq_config.json"));
53  } else {
54  fs.writeFileSync("xdaq_config.json", JSON.stringify(config));
55  }
56 
57  if (config.hostname === "localhost" && cluster.isMaster) {
58  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.");
59  }
60 }
61 
62 loadConfig();
63 var log_file = fs.createWriteStream(config.logFileName, { flags: 'a' });
64 var log_stdout = process.stdout;
65 
66 console.log = function (d) { //
67  log_file.write((new Date().toString()) + ": " + util.format(d) + '\n');
68  if (config.logConsoleEnabled) log_stdout.write(util.format(d) + '\n');
69 };
70 
71 function LoadCerts(path) {
72  if (!fs.existsSync(path)) {
73  console.log("Creating certificates directory");
74  fs.mkdirSync(path);
75  var cert = fs.createWriteStream(path_module.join(path, "cilogon-basic.pem"));
76  https.get("https://cilogon.org/cilogon-basic.pem", function (res) { res.pipe(cert); });
77  var cert2 = fs.createWriteStream(path_module.join(path, "cilogon-basic.crt"));
78  https.get("https://cilogon.org/cilogon-basic.crt", function (res) { res.pipe(cert2); });
79  }
80  var output = [];
81  var files = fs.readdirSync(path);
82  for (var i = 0; i < files.length; i++) {
83  if (files[i].search(".pem") > 0 || files[i].search(".crt") > 0) {
84  output.push(fs.readFileSync(path + "/" + files[i]));
85  }
86  }
87  return output;
88 }
89 
90 function GetCILogonCRL(path) {
91  // Always fetch the latest CRL lists from CILogon:
92  var file = fs.createWriteStream(path_module.join(path, "cilogon-basic.r0"));
93  http.get("http://crl-cilogon.ncsa-security.net/cilogon-basic.r0", function (res) { res.pipe(file); });
94  var file2 = fs.createWriteStream(path_module.join(path, "cilogon-basic.crl"));
95  http.get("http://crl-cilogon.ncsa-security.net/cilogon-basic.crl", function (res) { res.pipe(file2); });
96 }
97 
98 function LoadCRLs(path) {
99  if (!fs.existsSync(path)) {
100  console.log("Creating directory " + path);
101  fs.mkdirSync(path);
102  }
103  GetCILogonCRL(path);
104  var output = [];
105  var files = fs.readdirSync(path);
106  for (var i = 0; i < files.length; i++) {
107  if (files[i].search(".r0") > 0 || files[i].search(".crl") > 0) {
108  output.push(fs.readFileSync(path + "/" + files[i]));
109  }
110  }
111  return output;
112 }
113 
114 // Node.js by default is single-threaded. Start multiple servers sharing
115 // the same port so that an error doesn't bring the whole system down
116 if (cluster.isMaster) {
117 
118  // Start workers for each CPU on the host
119  for (var i = 0; i < numCPUs; i++) {
120  //for (var i = 0; i < 1; i++) {
121  var worker = cluster.fork();
122  }
123 
124  // If one dies, start a new one!
125  cluster.on("exit", function () {
126  var newWorker = cluster.fork();
127  });
128 } else {
129  // Node.js framework "includes"
130  var https = require('https');
131  var http = require('http');
132  var url = require('url');
133 
134  console.log("Setting up options");
135  var options = {
136  key: fs.readFileSync('./certs/server.key'),
137  cert: fs.readFileSync('./certs/server.crt'),
138  ca: LoadCerts("./certs/certificates"),
139  crl: LoadCRLs("./certs/certificates"),
140  requestCert: true,
141  rejectUnauthorized: false
142  };
143  var authlist = " " + fs.readFileSync("./certs/authorized_users");
144  console.log("Done setting up options");
145 
146  // Make an http server
147  var server = https.createServer(options, function (req, res) {
148  try {
149  console.log("Getting client certificate");
150  var clientCertificate = req.connection.getPeerCertificate();
151  var username ="";
152  var useremail = "";
153  var fp = "";
154  console.log(JSON.stringify(clientCertificate));
155 
156  if(clientCertificate && clientCertificate.subject) {
157  console.log("Getting certificate info...");
158  username = clientCertificate.subject.CN[0];
159  useremail = clientCertificate.subjectaltname.substr(6);
160  fp = clientCertificate.modulus;
161 
162  fs.writeFileSync(config.certInfoFile, useremail + "\n" + fp);
163  setTimeout(function () {
164  if (fs.existsSync(config.certInfoFile) && (Date.now() - fs.statSync(config.certInfoFile).mtime) > 1000) {
165  fs.unlinkSync(config.certInfoFile);
166  }
167  }, 1000);
168  }
169 
170  console.log("Checking if client is authorized");
171  //console.log("User: " + username + ", Email: " + useremail);
172  //console.log(JSON.stringify(req.connection));
173  if (req.client.authorized) {
174  //console.log(JSON.stringify(clientCertificate));
175  //var org = clientCertificate.subject.O[0];
176  //if (org !== "Fermi National Accelerator Laboratory") {
177  // readOnly = true;
178  //}
179  if (authlist.search(username) > 0 || authlist.search(useremail) > 0) {
180  console.log("User: " + username + " (" + useremail + ")");
181  }
182  }
183 
184  if (req.url.search(/lid=\d+$/) > 0) {
185  req.url = req.url + "/";
186  }
187 
188  var thisurl = url.parse(req.url, true);
189  //console.log("Request Querystring before: " + JSON.stringify(thisurl.query));
190  thisurl.search = "";
191 
192  console.log("Request path: " + req.url);
193  if (fp.length > 0 && req.url.search("RequestType=cert") > 0) {
194  thisurl.query.httpsUser = fp;
195  }
196  //console.log("Request Querystring after: " + JSON.stringify(thisurl.query));
197  //console.log(JSON.stringify(thisurl));
198  var pathname = url.format(thisurl);
199  console.log("Adjusted path: " + pathname);
200 
201  if (config.xdaqmode && (pathname === "/" || req.url === "/")) {
202  var code = 302;
203  var redirUrl = "https://" + config.listenhost + ":" + config.listenport + "/urn:xdaq-application:lid=200/";
204 
205  res.writeHead(code, { 'location': redirUrl });
206  res.end();
207  }
208  else {
209  var reqOptions = {
210  host: config.xdaqhost,
211  port: config.xdaqport,
212  method: req.method,
213  headers: req.headers,
214  path: pathname
215  }
216  //console.log("Request options: " + JSON.stringify(reqOptions));
217  var xreq = http.request(reqOptions, function (xres) {
218  if (xres.statusCode >= 300 && xres.statusCode < 400 && xres.headers.location) {
219  console.log("Redirect detected. Going to " + xres.headers.location);
220  var redirUri = url.parse(xres.headers.location + "/");
221  redirUri.hostname = config.listenhost;
222  redirUri.port = config.listenport;
223  redirUri.host = config.listenhost + ":" + config.listenport;
224  redirUri.protocol = "https:";
225  var redirUrl = url.format(redirUri);
226  console.log("Redirect url adjusted to " + redirUrl);
227  res.writeHead(xres.statusCode, { 'location': redirUrl });
228  res.end();
229  } else {
230  //console.log("Piping xres into res");
231  xres.pipe(res);
232  }
233  });
234  //console.log("Piping req into xreq");
235  req.pipe(xreq);
236  }
237  } catch (e) {
238  console.log("Unhandled error in server: " + JSON.stringify(e));
239  console.trace("Unhandled error in server: " + JSON.stringify(e));
240  }
241  });
242 
243  console.log("Listening on https://" + config.listenhost + ":" + config.listenport);
244  server.listen(config.listenport, config.listenhost);
245 }