From 32d4b4059496514b9d07892018fbf8024b525394 Mon Sep 17 00:00:00 2001
From: Dylan Hicks <dylanh333@outlook.com>
Date: Mon, 11 Jun 2018 22:03:09 +0800
Subject: [PATCH] Lots of changes - including init.js no longer needing root,
 and a (somewhat) working stub for login-service.js

---
 .gitignore                           |  2 +-
 dylan-testing/common/config.js       | 15 ++++++
 dylan-testing/common/constants.js    |  3 ++
 dylan-testing/common/socket-proxy.js | 44 +++++++++++++++
 dylan-testing/config.json            |  7 ++-
 dylan-testing/core/init.js           | 80 ++++++++++++++++------------
 dylan-testing/core/login-service.js  | 20 ++++++-
 dylan-testing/core/pam-service.js    |  1 -
 dylan-testing/core/proxy-service.js  |  1 -
 dylan-testing/core/web-service.js    | 25 +++++++++
 dylan-testing/package.json           |  5 +-
 11 files changed, 163 insertions(+), 40 deletions(-)
 create mode 100644 dylan-testing/common/config.js
 create mode 100644 dylan-testing/common/constants.js
 create mode 100644 dylan-testing/common/socket-proxy.js
 delete mode 100644 dylan-testing/core/pam-service.js
 delete mode 100644 dylan-testing/core/proxy-service.js
 create mode 100644 dylan-testing/core/web-service.js

diff --git a/.gitignore b/.gitignore
index 959fcc6..de70e33 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,4 +70,4 @@ typings/
 
 # Ignore temporary backup files created by vim
 *~
-.swp
+*.swp
diff --git a/dylan-testing/common/config.js b/dylan-testing/common/config.js
new file mode 100644
index 0000000..f17ca72
--- /dev/null
+++ b/dylan-testing/common/config.js
@@ -0,0 +1,15 @@
+function getConfig(){
+    var config;
+
+    try {config = JSON.parse(process.env.UCCPORTAL_CONFIG);}
+    catch(e){
+        throw new Error(
+            "Failed to retrieve config from UCCPORTAL_CONFIG "
+            + "environment variable: " + e.message
+        );
+    }
+
+    return config;
+}
+
+module.exports = getConfig();
diff --git a/dylan-testing/common/constants.js b/dylan-testing/common/constants.js
new file mode 100644
index 0000000..87d9c75
--- /dev/null
+++ b/dylan-testing/common/constants.js
@@ -0,0 +1,3 @@
+module.exports = {
+    
+};
diff --git a/dylan-testing/common/socket-proxy.js b/dylan-testing/common/socket-proxy.js
new file mode 100644
index 0000000..bb91afa
--- /dev/null
+++ b/dylan-testing/common/socket-proxy.js
@@ -0,0 +1,44 @@
+var fs = require("fs");
+
+/**
+ * Creates a new middleware for express that proxies all requests to the
+ * specified UNIX domain socket.
+ *
+ * @param {string} socketPath - the path to the socket
+ * @return {function} - an express middleware function
+ */
+module.exports = function socketProxy(socketPath){
+    return function socketProxyMiddleware(req, res, next){
+        fs.stat(socketPath, function(err, stats){
+            // Check that we can actually open the socket,
+            // and that it's a socket
+            try {
+                if(err) throw err;
+                if(!stats.isSocket()) throw new Error("Not a socket");
+            }
+            catch(e){
+                res.status(500).end();
+                console.error(
+                    "Error proxying to socket \"" + socketPath + "\": "
+                    + e.message
+                );
+                return;
+            }
+
+            // Initiate HTTP request to proxy target
+            var reqOpts = {
+                socketPath: socketPath,
+                method: req.method,
+                path: req.path,
+                headers: req.headers
+            };
+            var target = http.request(reqOpts, function onResponse(targetRes){
+                res.writeHead(targetRes.statusCode, targetRes.headers);
+                targetRes.pipe(res);
+                // .pipe() will automatically call res.end()
+            });
+            req.pipe(target);
+        });
+    };
+}
+
diff --git a/dylan-testing/config.json b/dylan-testing/config.json
index f30002b..f9a4463 100644
--- a/dylan-testing/config.json
+++ b/dylan-testing/config.json
@@ -1,4 +1,7 @@
 {
-	"serviceUser":"uccportal",
-	"varPath":"/var/lib/uccportal"
+	"configPath":"/etc/uccportal/config.json",
+	"varPath":"/var/lib/uccportal",
+    "user":"uccportal",
+    "host":"0.0.0.0",
+    "port":8080
 }
diff --git a/dylan-testing/core/init.js b/dylan-testing/core/init.js
index 314219a..cfe5b85 100644
--- a/dylan-testing/core/init.js
+++ b/dylan-testing/core/init.js
@@ -20,10 +20,11 @@ function getUserIds(username){
     return ids;
 }
 
-function initConfig(configPath){
+function initConfig(configPath, shadowConfig){
     var rawConfig, config;
 
     // Try to open config file
+    console.info("Loading config from \"" + configPath + "\"");
     try {rawConfig = fs.readFileSync(configPath);}
     catch(e){
         throw new Error(
@@ -32,7 +33,12 @@ function initConfig(configPath){
     }
     
     // Try to parse config file as JSON
-    try {config = JSON.parse(rawConfig);}
+    try {
+        // Make config inherit properties from shadowConfig, so it they're
+        // undefined in the current config file, their defaults take precedence
+        // from the default config file.
+        config = Object.assign({}, shadowConfig, JSON.parse(rawConfig));
+    }
     catch(e){
         throw new Error(
             "\"" + configPath + "\" is not in a valid JSON format: "
@@ -51,12 +57,23 @@ function initConfig(configPath){
             );
         }
 
-        // serviceUser: verify that a user and group of this name exist,
-        // then set serviceUid and serviceGid for later lookup
-        var ids = getUserIds(config.serviceUser);
-        config.serviceUid = ids.uid;
-        config.serviceGid = ids.gid;
+        // user: verify that a user and group of this name exist,
+        // then set uid and gid for later lookup
+        var ids = getUserIds(config.user);
+        config.uid = ids.uid;
+        config.gid = ids.gid;
 
+        // host: verify that host is in a (somewhat) valid format
+        // TODO: Improve this?
+        if(typeof config.host != "string" || !config.host.match(
+            /[a-z0-9:.-]+/i
+        )) throw new Error("host: not a valid hostname nor IP address");
+
+        // port: verify that a valid port was specified
+        if(
+            typeof config.port != "number"
+            || config.port%1 > 0 || config.port < 0 || config.port > 65536
+        ) throw new Error("port: invalid port number specified");
     }
     catch(e){
         throw new Error(
@@ -64,44 +81,41 @@ function initConfig(configPath){
         );
     }
 
+    // configPath: if the config file specifies a different path to a config
+    // file, then try to load this instead.
+    // Config files loaded later inherit from config files loaded earlier :)
+    if(config.configPath){
+        // Make sure that we don't keep re-loading the current config file
+        // recursively and overflow the stack.
+        configPath = config.configPath;
+        delete config.configPath
+        return initConfig(configPath, config);
+    }
+
     return config;
 }
 
 module.exports = function main(){
 	try {
-        // Verify that we're currently running as root
-        if(process.getuid() != 0){
-            throw new Error(
-                "UCCPortal must be run as root to function correctly.\n"
-                + "If you're worried about security, privileges will be dropped "
-                + "to that of the serviceUser listed in config.json, once "
-                + "initialisation is complete. An exception to this is "
-                + "auth-service.js, which requires it for PAM to function "
-                + "correctly."
-            );
-        }
-        
-        // Attempt to load config.json, or fail
+        // Attempt to load config.json
         var config = initConfig("config.json");
-        console.dir(config);
-        
+
+        // Set up environment for child processes
+        process.env.UCCPORTAL_CONFIG = JSON.stringify(config);
+        process.env.NODE_PATH = 
+            (process.env.NODE_PATH ? process.env.NODE_PATH + ":" : "")
+            + process.cwd()
+        ;
+
         // Launch services
         var nodejs = process.argv[0];
-        var spawnOpts = {
-            stdio:"inherit", uid:config.serviceUid, gid:config.serviceGid
-        };
-        var rootSpawnOpts = {stdio:"inherit", gid:config.serviceGid};
-        cproc.spawn(nodejs, ["core/pam-service.js"], rootSpawnOpts);
-        cproc.spawn(nodejs, ["core/proxy-service.js"], spawnOpts);
+        var spawnOpts = {stdio: "inherit"};
+        cproc.spawn(nodejs, ["core/web-service.js"], spawnOpts);
         cproc.spawn(nodejs, ["core/login-service.js"], spawnOpts);
-
-        // Drop privileges
-        process.setgid(config.serviceGid);
-        process.setuid(config.serviceUid);
     }
     catch(e){
         //console.error("Fatal Error: " + e.message);
-        throw e;
         process.exitCode = 1;
+        throw e;
     }
 }
diff --git a/dylan-testing/core/login-service.js b/dylan-testing/core/login-service.js
index 664ade7..fcbc241 100644
--- a/dylan-testing/core/login-service.js
+++ b/dylan-testing/core/login-service.js
@@ -1 +1,19 @@
-while(true);
+var express = require("express");
+var fs = require("fs");
+
+var config;
+var app;
+
+function main(){
+    // Read in config from environment
+    config = JSON.parse(process.env.UCCPORTAL_CONFIG);
+    
+    app = express();
+    app.get("/", function(req, res){
+        res.write("Hello World!");
+        res.end();
+    });
+
+    app.listen(config.varPath + "/login-service.sock");
+}
+main();
diff --git a/dylan-testing/core/pam-service.js b/dylan-testing/core/pam-service.js
deleted file mode 100644
index 664ade7..0000000
--- a/dylan-testing/core/pam-service.js
+++ /dev/null
@@ -1 +0,0 @@
-while(true);
diff --git a/dylan-testing/core/proxy-service.js b/dylan-testing/core/proxy-service.js
deleted file mode 100644
index 664ade7..0000000
--- a/dylan-testing/core/proxy-service.js
+++ /dev/null
@@ -1 +0,0 @@
-while(true);
diff --git a/dylan-testing/core/web-service.js b/dylan-testing/core/web-service.js
new file mode 100644
index 0000000..cef2e70
--- /dev/null
+++ b/dylan-testing/core/web-service.js
@@ -0,0 +1,25 @@
+var express = require("express");
+var http = require("http");
+var fs = require("fs");
+var config = require("common/config.js");
+var socketProxy = require("common/socket-proxy.js");
+
+var config;
+var app;
+
+function main(){
+    // Set up express app and routes
+    app = express();
+    app.use("/login", socketProxy(config.varPath + "/login-service.sock"));
+
+    // Start web server or crash
+    console.info(
+        "Starting web-service on host \"" + config.host
+        + "\" and port " + config.port
+    );
+    try {app.listen(config.port, config.host);}
+    catch(e){
+        throw new Error("Failed to start web-service: " + e.message);
+    }
+}
+main();
diff --git a/dylan-testing/package.json b/dylan-testing/package.json
index 94c7b19..74539c6 100644
--- a/dylan-testing/package.json
+++ b/dylan-testing/package.json
@@ -11,5 +11,8 @@
     "url": "https://gitlab.ucc.asn.au/frekk/uccportal.git"
   },
   "author": "dylanh333; frekk",
-  "license": "GPL-3.0"
+  "license": "GPL-3.0",
+  "dependencies": {
+    "express": "^4.16.3"
+  }
 }
-- 
GitLab