*** empty log message ***
authorMatthew Mondor <mmondor@pulsar-zone.net>
Sun, 20 Aug 2006 06:59:00 +0000 (06:59 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Sun, 20 Aug 2006 06:59:00 +0000 (06:59 +0000)
mmsoftware/js/js-appserv/app/test/handle.js [new file with mode: 0644]
mmsoftware/js/js-appserv/app/test/main.js [new file with mode: 0644]
mmsoftware/js/js-appserv/app/test/session.js [new file with mode: 0644]
mmsoftware/js/js-appserv/app/test/test.conf [new file with mode: 0644]
mmsoftware/js/js-appserv/doc/js-appserv-protocol.lyx [new file with mode: 0644]
mmsoftware/js/js-appserv/src/GNUmakefile [new file with mode: 0644]
mmsoftware/js/js-appserv/src/js-appserv.8 [new file with mode: 0644]
mmsoftware/js/js-appserv/src/js-appserv.c [new file with mode: 0644]
mmsoftware/js/js-appserv/src/js-appserv.conf.5 [new file with mode: 0644]

diff --git a/mmsoftware/js/js-appserv/app/test/handle.js b/mmsoftware/js/js-appserv/app/test/handle.js
new file mode 100644 (file)
index 0000000..acaebbe
--- /dev/null
@@ -0,0 +1,53 @@
+/* $Id: handle.js,v 1.1 2006/08/20 06:58:57 mmondor Exp $ */
+
+/*
+ * Copyright (C) 2006, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by Matthew Mondor.
+ * 4. The name of Matthew Mondor may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ * 5. Redistribution of source code may not be released under the terms of
+ *    any GNU Public License derivate.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This function handles every request, provided with session id and
+ * request object.
+ */
+function handle(req)
+{
+       /* XXX Only test for now */
+       var o = {result: true, req: req, msg: "NOOP test"};
+       fd.put(o.toSource() + "\r\n");
+
+       return true;
+}
+
+/* Perform application-specific saving before connection close */
+function handle_close()
+{
+       /* XXX */
+}
diff --git a/mmsoftware/js/js-appserv/app/test/main.js b/mmsoftware/js/js-appserv/app/test/main.js
new file mode 100644 (file)
index 0000000..ed3d125
--- /dev/null
@@ -0,0 +1,356 @@
+/* $Id: main.js,v 1.1 2006/08/20 06:58:57 mmondor Exp $ */
+
+/*
+ * Copyright (C) 2006, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by Matthew Mondor.
+ * 4. The name of Matthew Mondor may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ * 5. Redistribution of source code may not be released under the terms of
+ *    any GNU Public License derivate.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Test application for js-appserv.
+ * See the mmserver2(3) man page for more details.
+ *
+ * All of these functions should be provided, although they may optionally
+ * be empty.
+ */
+
+/*
+ * TODO:
+ * - js-appserv should export C->js functions to:
+ *   - create SIDs
+ *   - import and export single line JSON-like objects in a safe and efficient
+ *     manner
+ *   - Access shared memory hash tables and properties (maybe)
+ *   - Log events
+ *   - Create contexts, and cache/load/execute scripts within the wanted
+ *     context
+ * - Probably also use some config.js
+ * - Define all details of the protocol, allowed request types, etc.
+ *   - Should include ping/pong functionality for extended idle persistent
+ *     connections
+ *   - Should have a maximum delay where if no requests are received but
+ *     session not yet expired, session data be saved and connection freed
+ *     for other requests
+ *   - Reconsider if using HTTP 1.1 with Keep-Alive would be justified.
+ *     Despite that the protocol is lame (unidirectional, doesn't allow
+ *     pings, awkward etc), this would permit to use simple proxy support
+ *     already existing in some HTTPds...
+ *   - Would be nice if we also allow logging of errors (stderr?) back to
+ *     the main HTTPd, perhaps, like fcgi does.
+ * - Write/modify an HTTPd to support this protocol
+ *   - Also provide a routes or such system
+ *   - Provide load balancing among several js-appserv daemons, as well as
+ *     failover
+ * - Add optional JS debugging support for development phases which can be
+ *   disabled for production, possibly via a special server socket, or
+ *   via an application for use from HTTP
+ * - Import lithium's js preprocessor code or write mine
+ * - Import lithium's web application framework or write mine
+ * - Implement a cooperative client and server-side rfc2945 or hmac auth
+ *   system
+ * - Add authentication support for wanted HTTPd only to use the system
+ * - Add optional compression and encryption between the HTTPd and the
+ *   js-appserv.  If doing this, we'll need to provide read/write/flush
+ *   wrappers to JS.
+ * - Add stderr to logfile option
+ */
+
+ilibprefix = '/home/mmondor/work/mmondor/mmsoftware/js/jslib/';
+iappprefix = '/home/mmondor/work/mmondor/mmsoftware/js/js-appserv/app/test/';
+hello_timeout = 30000;
+request_timeout = 180000;
+database = "dbname=test";
+sid_expiration = 30;
+
+
+
+/* Set err globally */
+err = new FD();
+err.set(FD.STDERR_FILENO);
+
+
+
+function file_read(file)
+{
+       var contents = '';
+       var data = '';
+       var fd;
+
+       try {
+               fd = new FD();
+               fd.open(file, FD.O_RDONLY);
+               for (;;) {
+                       data = fd.read(65536);
+                       if (data.length == 0)
+                               break;
+                       contents += data;
+               }
+       } catch (x) {
+               err.put(x + "\n");
+       } finally {
+               fd.close();
+       }
+
+       return contents;
+}
+
+
+
+eval(file_read(ilibprefix + "fd.js"));
+eval(file_read(ilibprefix + "pgsql.js"));
+eval(file_read(ilibprefix + "string.js"));
+eval(file_read(ilibprefix + "json.js"));
+eval(file_read(iappprefix + "session.js"));
+eval(file_read(iappprefix + "handle.js"));
+
+
+
+/*
+ * Control for the children server processes
+ */
+
+/* Called when a new child process is created before serving requests */
+function child_init_hook()
+{
+       try {
+               /* Set pgc globally */
+               pgc = PG.connectDb(database);
+       } catch (x) {
+               err.put(x + "\n");
+       }
+       sid_init();
+}
+
+/* Called before the child process exists (interrupted or not) */
+function child_exit_hook()
+{
+       sid_gc();               /* XXX Not an ideal place for this */
+       sid_finish();
+       pgc.finish();
+}
+
+/* Called upon SIGALRM signal events (recursion-protected) */
+function child_sigalrm_hook()
+{
+       /* XXX Handle application specific timers with this */
+}
+
+
+/*
+ * Control for handling clients
+ */
+
+/* Called if client has to be rejected for <reason> */
+function reject_handler(r, reason)
+{
+       var o;
+
+       try {
+               fd = new FD();
+               fd.binit(r.client_socket, hello_timeout, hello_timeout);
+       } catch (x) {
+               err.put(x + "\n");
+       }
+
+       try {
+               o = {result: false, msg: "Rejected", reason: reason};
+               fd.put(o.toSource() + "\r\n");
+       } catch (x) {
+               err.put(x + "\n");
+       }
+}
+
+/* Called before closing request connection, rejected/interrupted or not */
+function request_close_hook(r)
+{
+       if (SID != undefined && DATA != undefined) {
+               /* Call application specific save hook if any */
+               if (handle_close != undefined)
+                       handle_close();
+               /* Save SID state */
+               sid_save(SID, DATA);
+               SID = undefined;
+               DATA = undefined;
+       }
+}
+
+/*
+ * Called if our application allows SIGHUP or SIGTERM to interrupt currently
+ * active requests, upon reception of such signal event, before closing
+ */
+function request_interrupt_hook(r)
+{
+       /* XXX */
+}
+
+/* Normal client request handling function */
+function request_handler(r)
+{
+       var line;
+       var o;
+       var session;
+       var request;
+
+       try {
+               fd = new FD();
+               fd.binit(r.client_socket, hello_timeout, hello_timeout);
+       } catch (x) {
+               err.put(x + "\n");
+       }
+
+       o = {result: true, msg: "Ready"};
+       fd.put(o.toSource() + "\r\n");
+
+       /* Listen for incoming requests for that session */
+       for (;;) {
+               if ((line = fd.breadline(16384, true)) == null) {
+                       o = {result: false, msg: fd.berrorStr[fd.berror]};
+                       fd.put(o.toSource() + "\r\n");
+                       return;
+               }
+               try {
+                       request = json_parse(line, 10);
+                       if (request == undefined)
+                               throw(json_error);
+               } catch (x) {
+                       o = {result: false, msg: "Malformed request", x: x};
+                       fd.put(o.toSource() + "\r\n");
+                       return;
+               }
+               try {
+                       /* Handle a few special system requests */
+                       if (request.cmd == 'sid_create') {
+                               if (SID != undefined) {
+                                       o = {result: false,
+                                           msg: "Already serving a SID"};
+                                       fd.put(o.toSource() + "\r\n");
+                                       continue;
+                               }
+                               if ((o = sid_create(sid_expiration))
+                                   == undefined) {
+                                       o = {result: false,
+                                           msg: "Error creating SID"};
+                                       fd.put(o.toSource() + "\r\n");
+                                       continue;
+                               }
+                               SID = o.sid;
+                               EXPIRES = o.expires;
+                               DATA = {};
+                               o = {result: true, msg: "Created", sid: SID};
+                               fd.btimeouts(request_timeout, request_timeout);
+                               fd.put(o.toSource() + "\r\n");
+                               continue;
+                       } else if (request.cmd == 'sid_destroy' &&
+                           request.sid != undefined) {
+                               if (request.sid != SID) {
+                                       o = {result: false,
+                                           msg: "Not current SID"};
+                                       fd.put(o.toSource() + "\r\n");
+                                       continue;
+                               }
+                               sid_destroy(SID);
+                               SID = undefined;
+                               EXPIRES = undefined;
+                               DATA = undefined;
+                               o = {result: true, msg: "SID interrupted"};
+                               fd.put(o.toSource() + "\r\n");
+                               return;
+                       } else if (request.cmd == 'sid_load' &&
+                           request.sid != undefined &&
+                           request.sid.length == 64) {
+                               if (SID != undefined) {
+                                       o = {result: false,
+                                           msg: "Already serving a SID"};
+                                       fd.put(o.toSource() + "\r\n");
+                                       continue;
+                               }
+                               if ((o = sid_load(request.sid)) == undefined) {
+                                       o = {result: false,
+                                           msg: "Error loading SID data"};
+                                       fd.put(o.toSource() + "\r\n");
+                                       continue;
+                               } else {
+                                       SID = request.sid;
+                                       EXPIRES = o.expires;
+                                       DATA = o.data;
+                                       o = {result: true,
+                                           msg: "SID data loaded"};
+                                       fd.btimeouts(request_timeout,
+                                           request_timeout);
+                                       fd.put(o.toSource() + "\r\n");
+                                       continue;
+                               }
+                       } else if (request.cmd == 'ping') {
+                               o = {result: true, msg: "Pong"};
+                               fd.put(o.toSource() + "\r\n");
+                               continue;
+                       } else if (request.cmd == 'quit') {
+                               o = {result: true, msg:
+                                   (SID != undefined ? "SID data saved" :
+                                   "Closing")};
+                               fd.put(o.toSource() + "\r\n");
+                               return;
+                       }
+
+                       /*
+                        * We should only go beyond this point if serving
+                        * a valid, unexpired SID.
+                        */
+                       if (SID == undefined || DATA == undefined) {
+                               o = {result: true, msg: "No active SID"};
+                               fd.put(o.toSource() + "\r\n");
+                               continue;
+                       }
+                       if (EXPIRES < Date.parse(new Date)) {
+                               o = {result: false, msg: "SID expired"};
+                               fd.put(o.toSource() + "\r\n");
+                               return;
+                       }
+
+                       /* Handle user application requests */
+                       if (!handle(request)) {
+                               /* Destroy SID */
+                               if (SID != undefined) {
+                                       sid_destroy(SID);
+                                       SID = undefined;
+                                       EXPIRES = undefined;
+                                       DATA = undefined;
+                               }
+                               o = {result: false, command: "delete"};
+                               fd.put(o.toSource() + "\r\n");
+                               return;
+                       }
+               } catch (x) {
+                       o = {result: false, msg: "Application error",
+                           exception: x};
+                       fd.put(o.toSource() + "\r\n");
+               }
+       }
+}
diff --git a/mmsoftware/js/js-appserv/app/test/session.js b/mmsoftware/js/js-appserv/app/test/session.js
new file mode 100644 (file)
index 0000000..aea1cf1
--- /dev/null
@@ -0,0 +1,249 @@
+/* $Id: session.js,v 1.1 2006/08/20 06:58:57 mmondor Exp $ */
+
+/*
+ * Copyright (C) 2006, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by Matthew Mondor.
+ * 4. The name of Matthew Mondor may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ * 5. Redistribution of source code may not be released under the terms of
+ *    any GNU Public License derivate.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Code to manage sessions and session-specific persistent data
+ *
+ * session table should be as follows:
+ *
+ * CREATE TABLE session (
+ *     sid     bytea           NOT NULL,
+ *     expires timestamp       NOT NULL,
+ *     valid   boolean         NOT NULL DEFAULT true,
+ *     busy    boolean         NOT NULL DEFAULT true,
+ *     data    text            NOT NULL DEFAULT '({})',
+ *     PRIMARY KEY (sid)
+ * );
+ *
+ * sid         consists of a 64-bytes case-sensitive ASCII session ID.
+ * expires     specifies the time at which this sid stops being valid.
+ * valid       specifies if this sid is still valid, to invalidate before
+ *             expiration, such as when logging out.
+ * busy                specifies that this sid is currently being served by a
+ *             process, useful when using distributed appserv on multiple
+ *             boxes.
+ * data                contains a string, resulting of DATA.toSource(), to hold
+ *             the user session-specific persistent data.
+ */
+
+
+
+var SID = undefined;
+var DATA = {};
+
+
+
+/*
+ * Application initialization
+ */
+function sid_init()
+{
+       var pgr;
+
+       try {
+               sid_rnd = new FD();
+               sid_rnd.open('/dev/urandom', FD.O_RDONLY);
+       } catch (x) {
+               /* XXX */
+       }
+
+       /* valid = true, busy = true */
+       pgr = pgc.prepare("sid_create",
+           "INSERT INTO session (sid, expires) " +
+           "VALUES($1, 'now'::timestamp + $2)",
+           2, null);
+       pgr.clear();
+
+       pgr = pgc.prepare("sid_load1",
+           "UPDATE session SET busy = true WHERE sid = $1 " +
+           "AND expires >= 'now'::timestamp " +
+           "AND valid = true AND busy = false",
+           1, null);
+       pgr.clear();
+       pgr = pgc.prepare("sid_load2",
+           "SELECT expires::timestamp(0),data FROM session WHERE sid = $1",
+           1, null);
+       pgr.clear();
+
+       pgr = pgc.prepare("sid_save",
+           "UPDATE session SET data = $2, busy = false WHERE sid = $1",
+           2, null);
+       pgr.clear();
+
+       pgr = pgc.prepare("sid_destroy",
+           "UPDATE session SET valid = false, busy = false WHERE sid = $1",
+           1, null);
+       pgr.clear();
+
+       pgr = pgc.prepare("sid_gc",
+           "DELETE FROM session WHERE " +
+           "expires < 'now'::timestamp " +
+           "OR valid = false",
+           0, null);
+       pgr.clear();
+
+       sid_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
+           'abcdefghijklmnopqrstuvwxyz' +
+           '0123456789-_';
+       sid_chars = sid_chars.split('');
+}
+
+/*
+ * Application cleanup
+ */
+function sid_finish()
+{
+
+       sid_rnd.close();
+}
+
+/*
+ * Generate and create new SID, making it active (busy).
+ * Returns an object, containing:
+ * <sid> new sid
+ * <expires> expiration timestamp in milliseconds (to compare against current
+ * time in milliseconds).
+ */
+function sid_create(exp)
+{
+       var rval;
+       var pgr;
+       var sid;
+       var i;
+       var o = {};
+
+       /*
+        * XXX Should be done from C for efficiency and to avoid needing so
+        * many bytes off urandom(4).
+        */
+       try {
+               rval = sid_rnd.read(64);
+       } catch (x) {
+               /* XXX */
+       }
+       sid = '';
+       /* This is apparently slower for some reason
+       for (i = 0; i < 64; i++)
+               sid += sid_chars.charAt(rval.charCodeAt(i) % 64);
+        */
+       rval = rval.split('');
+       for (i = 0; i < 64; i++)
+               sid += sid_chars[rval[i].charCodeAt() % 64];
+
+       o.expires = Date.parse(new Date) + (exp * 60000);
+
+       pgr = pgc.execPrepared("sid_create",
+           2, [sid, exp + 'minutes'], [64, 0], [1, 0], 0);
+       if (pgr.resultStatus() != PG.PGRES_COMMAND_OK)
+               sid = undefined;
+       pgr.clear();
+
+       if (sid == undefined)
+               o = undefined;
+       else
+               o.sid = sid;
+
+       return o;
+}
+
+/*
+ * Load SID if still valid not already busy, and mark it busy.
+ * Returns an object containing:
+ * <data> the imported session-specific data
+ * <expires> the expiration timstamp in milliseconds (to be compared against
+ * current time in milliseconds).
+ */
+function sid_load(sid)
+{
+       var pgr1, pgr2;
+       var s;
+       var o = {};
+
+       pgr1 = pgc.execPrepared("sid_load1", 1, [sid], [64], [1], 0);
+       if (pgr1.resultStatus() == PG.PGRES_COMMAND_OK &&
+           pgr1.cmdTuples() == 1) {
+               pgr2 = pgc.execPrepared("sid_load2", 1, [sid], [64], [1], 0);
+               if (pgr2.resultStatus() == PG.PGRES_TUPLES_OK &&
+                   pgr2.nTuples() == 1) {
+                       o.expires = Date.parse(pgr2.getValue(0, 0).replace(
+                           /-/g, '/'));
+                       s = 'o.data = ' + pgr2.getValue(0, 1);
+                       eval(s);
+               } else
+                      o = undefined;
+               pgr2.clear();
+       } else
+              o = undefined;
+       pgr1.clear();
+
+       return o;
+}
+
+/*
+ * Save/close SID and unmark busy flag
+ */
+function sid_save(sid, o)
+{
+       var pgr;
+       var s;
+
+       s = pgc.escapeStringConn(o.toSource());
+       pgr = pgc.execPrepared("sid_save", 2,
+           [sid, s], [64, s.length], [1, 0], 0);
+       pgr.clear();
+}
+
+/*
+ * Close SID, unmark busy flag and invalidate it
+ */
+function sid_destroy(sid)
+{
+       var pgr;
+
+       pgr = pgc.execPrepared("sid_destroy", 1, [sid], [64], [1], 0);
+       pgr.clear();
+}
+
+/*
+ * Delete all expired or invalid unbusy SIDs.
+ * To be called at spaced intervals.
+ */
+function sid_gc()
+{
+       var pgr;
+
+       pgr = pgc.execPrepared("sid_gc", 0, null, null, null, 0);
+       pgr.clear();
+}
diff --git a/mmsoftware/js/js-appserv/app/test/test.conf b/mmsoftware/js/js-appserv/app/test/test.conf
new file mode 100644 (file)
index 0000000..85b7e9f
--- /dev/null
@@ -0,0 +1,8 @@
+SCRIPT_PATH    "/home/mmondor/work/mmondor/mmsoftware/js/js-appserv/app/test/main.js"
+LISTEN_TO      "0.0.0.0:8100"
+PROCTITLE      "test"
+USER           "mmondor"
+GROUPS         "users"
+PID_PATH       "/tmp/js-appserv_test.pid"
+LOCK_PATH      "/tmp/js-appserv_test.lock"
+SHLOCKS_PATH   "/tmp/js-appserv_test.lock"
diff --git a/mmsoftware/js/js-appserv/doc/js-appserv-protocol.lyx b/mmsoftware/js/js-appserv/doc/js-appserv-protocol.lyx
new file mode 100644 (file)
index 0000000..e1789d5
--- /dev/null
@@ -0,0 +1,871 @@
+#LyX 1.3 created this file. For more info see http://www.lyx.org/
+\lyxformat 221
+\textclass article
+\language english
+\inputencoding auto
+\fontscheme default
+\graphics default
+\paperfontsize default
+\spacing single 
+\papersize letterpaper
+\paperpackage a4
+\use_geometry 0
+\use_amsmath 0
+\use_natbib 0
+\use_numerical_citations 0
+\paperorientation portrait
+\secnumdepth 3
+\tocdepth 3
+\paragraph_separation indent
+\defskip medskip
+\quotes_language english
+\quotes_times 2
+\papercolumns 1
+\papersides 1
+\paperpagestyle default
+
+\layout Title
+
+The JS-AppServ Protocol (JSASP)
+\layout Author
+
+By Matthew Mondor
+\layout Abstract
+
+The FastCGI protocol, although bearing significant advantages over plain
+ old CGI, and even often over HTTP-specific APIs, was considered an overly
+ complex protocol after an in-depth study.
+ Using binary format headers would not have been a problem by itself, but
+ representing multiple-octets words as separate octet fields instead of
+ using a big-endian representation, as well as using multiple header structure
+ types depending on the size of the data to be processed, instead of a fixed
+ sized word of the maximum needed size, seemed bad design.
+\layout Abstract
+
+Moreover, although this could be for the sake of flexibility, the numerous
+ ways to design server-side applications with FastCGI also can lead to problems.
+ Ultimately, the use of FastCGI appears most efficient with a heavily threaded
+ language such as Java, which language and threaded methodology have their
+ own set of problems.
+ Having the HTTPd launch application-server-side processes was also considered
+ unadequate.
+\layout Abstract
+
+This protocol is specifically designed to work on unix systems using an
+ independent, persistent process per user session, along with long-lasting
+ bidirectional socket connections between those persistent processes and
+ the HTTP daemon which initiates the requests.
+ This document describes the very details of the system.
+ For convenience, the 
+\noun on 
+JSON
+\noun default 
+ (
+\noun on 
+JavaScript Object Notation
+\noun default 
+) is used to transmit requests and responses.
+\layout Abstract
+
+Disdadventages exist; For instance this system is not compatible with the
+ existing CGI or FastCGI protocols.
+ Moreover, although parts could be re-implemented to support other languages,
+ the system was specifically made for use with the SpiderMonkey JavaScript
+ engine.
+ However, a provided compatible HTTPd is included as part of the system,
+ which is especially tailored to this model.
+\layout Abstract
+\pagebreak_top 
+
+\begin_inset LatexCommand \tableofcontents{}
+
+\end_inset 
+
+
+\layout Section
+\pagebreak_top 
+HTTPd specification
+\layout Subsection
+
+Model
+\layout Standard
+
+Within this system, the HTTP server model is simple and only requires a
+ single process.
+ Non-blocking I/O is used and the server merely serves as a proxy between
+ the js-appserv servers and the remote HTTP clients.
+\layout Standard
+
+This service should be as efficient as possible, since it must avoid becoming
+ a main bottleneck.
+ It should ideally be written in C and care should be taken to ensure that
+ no memory leaks or security problems are found.
+ Nevertheless, a re-spawner process could restart this service whenever
+ it dies or locks, if this situation ever was to occur.
+ If it is stable enough, the active session IDs and associated information
+ can be kept in RAM instead of having to be stored into a database.
+ It then remains up to the dynamic application to, as necessary, save database
+ information associated with this state if needed and to retreive it, etc.
+\layout Standard
+
+Optionally, the HTTPd can remember the client IP address associated with
+ the create SIDs to only keep accepting requests for that SID from that
+ very address, depending on the nature of the application.
+\layout Subsection
+
+Operations
+\layout Standard
+
+Here are the steps performed by the HTTP server when it obtains a request
+ from a user:
+\layout Itemize
+
+Verification weither the vhost/path combination was defined by the administrator
+ as being handled by a particular js-appserv application.
+ If not, serve static pages as requested if they exist and are accessible.
+\layout Itemize
+
+If path corresponds to an application and that the user did not provide
+ a unique session ID cookie which is still considered valid, generate a
+ new session ID for the user and send along an HTTP cookie to the client,
+ redirecting it to the same URL it requested with a few seconds of delay.
+\layout Itemize
+
+If path corresponds to an application and that the client provides us a
+ unique session ID which still is valid, verify if there already was an
+ established socket connection for this particular client/application pair.
+ If so, direct request to that application.
+ Otherwise, attempt to establish a socket connection to an application,
+ and then forward the request there.
+ The connection to the application remains open to serve further requests.
+\layout Itemize
+
+In the event where the session ID expires, or that the application initiates
+ a command to discard the session ID, the corresponding socket connection
+ to the application is closed and the entry for the session data is deleted.
+\layout Itemize
+
+In the event where the connection to an application is lost unexpectedly
+ or as the result of an inactivity timeout and that the session did not
+ expire, the session is marked as no longer having a connection, so that
+ it can be re-established on the next session.
+ It becomes the responsibility of the application to gracefully retrieve
+ the persistent session associated data as needed so that a session may
+ still resume normally.
+\layout Subsection
+
+Current implementation
+\layout Standard
+
+
+\emph on 
+XXX
+\layout Section
+
+Application (JS-AppServ) specification
+\layout Subsection
+
+Model
+\layout Standard
+
+The server uses a multi-processes model, along with a pool of pre-forked
+ processes for performance (Apache 1.3.x style).
+ Basically, there is a master controller process, along with a number of
+ children processes listening on the specified bound interface(s) and port(s),
+ serialization being used among them.
+\layout Standard
+
+These processes expect lenghty persistent connections originating from the
+ HTTP server, and generally survive after disconnection so that multiple
+ connections may be served without needing a process to be restarted everytime.
+ However, they can be configured to recycle after a number of connections
+ were served, in order to avoid long term resources leaks.
+ It is also possible for the process to exit as the result of an 
+\emph on 
+execve(2)
+\emph default 
+ call being completed in the same process, in which case it immediately
+ gets replaced by a new process.
+\layout Standard
+
+This model simplifies application design, avoiding the requirement of reentrant,
+ thread-safe, non-blocking and leakless code to be used, which can require
+ a considerable time to properly write and debug.
+ Moreover, it provides more security than using a single process, avoiding
+ a crash or exploit from propagating to the whole application.
+ Using a multi-processes model also allows additional tricks such as privilege
+ separation if the application requires this.
+\layout Standard
+
+js-appserv applications can run remotely from the HTTPd, or locally.
+ Moreover, it may run under other user credentials.
+ It can also run into a 
+\emph on 
+chroot(2)
+\emph default 
+ environment, sandboxed using 
+\emph on 
+systrace(1)
+\emph default 
+, etc.
+ Simple authentication is performed between the HTTPd and the js-appserv
+ daemons when the connections are initiated, in order to prevent unwanted
+ local or remote users from accessing the service.
+\emph on 
+Under local unix domain sockets, it is possible to authenticate using AF_LOCAL
+ ancillary data to determine that only the allowed user(s) can connect (XXX
+ to be implemented).
+\layout Standard
+
+Just like with FastCGI or other proxying distribution methods, it is still
+ important for the administrator to ensure that as few as possible entry
+ points are allowed.
+ This can be done by making the applications listen to an interface/port
+ combination to which only the HTTP server can connect, through a route
+ on which sniffing is considered impractical.
+\layout Standard
+
+
+\emph on 
+Optional data stream compression and encryption between the server and the
+ application will be provided, using a proprietary private key cryptography
+ method which is simpler although more efficient than SSL (XXX to be implemented
+).
+
+\emph default 
+ Care is taken to not disclose the password over the connection and to not
+ reuse existing encryption session keys.
+ There however is no guarentee whatsoever as to the security of the system.
+\layout Subsection
+
+Operations
+\layout Standard
+
+A typical js-appserv application child process behaves as follows:
+\layout Itemize
+
+Setup the JS context, loading the application scripts, preprocessing them
+ (can be done by the parent process and inherited by the children, telling
+ children to recycle upon reloading when signalled with 
+\emph on 
+SIGHUP
+\emph default 
+).
+ Note that in-progress connections can either be interrupted upon reception
+ of 
+\emph on 
+SIGHUP
+\emph default 
+ by the parent, or notified to exit whenever closing normally, depending
+ on the application requirements.
+ This is configurable via a configuration file.
+\layout Itemize
+
+Listen for a connection until one can be obtained.
+\layout Itemize
+
+Authenticate client/HTTPd, and drop it with an error immediately if it should
+ not be allowed, returning to connection waiting again.
+\layout Itemize
+
+Connection was accepted.
+ Retrieve SID and previously saved session persistent data if resuming,
+ or create a new SID depending on the context.
+ If loading, persistent session saved data is retrieved as well for unexpired,
+ valid SIDs.
+\layout Itemize
+
+The JS application now waits for requests and serves them.
+ These are expected to all be requests to serve the same user (under the
+ same SID).
+ Therefore it is possible to cache a lot of data for efficiency.
+\layout Itemize
+
+The application can send a response to discard the session ID and then exit,
+ causing the connection to be dropped and process to wait again for connections.
+ It can also drop the connection for inactivity timeout.
+ When closing connection for a still valid SID, persistent data is saved
+ to allow resuming under another connection (which generally will be served
+ by another process which will need to reload this data).
+\emph on 
+It is also possible for the application to use application-global shared
+ memory to cache some SID specific data (XXX to be implemented).
+\layout Itemize
+
+From time to time, the process will exit after serving a certain number
+ of persistent connections, to release dangling resources that it may be
+ leaking, if any.
+\layout Itemize
+
+The application manages the session-specific unique IDs, which the HTTP
+ translates to a cookie, 
+\emph on 
+GET
+\emph default 
+ or 
+\emph on 
+POST
+\emph default 
+ variable.
+ In this protocol, these IDs (Session ID or SID) consist of 64 character
+ strings which each character consists of one of a list of 64 characters.
+ As such, these can be efficiently generated using a reliable random source
+ of 32-bit or 64-bit input values, to generate batches of 4 to 8 characters
+ at a time of the SID.
+\emph on 
+A noonce is also used so that it is unlikely for two identical SIDs to ever
+ be used (XXX to be implemented).
+\layout Subsection
+
+Current implementation
+\layout Subsubsection
+
+JS-AppServ core
+\layout Standard
+
+The core of the server is writtein in C and uses the 
+\emph on 
+mmserver2(3)
+\emph default 
+ library, which easily allows support for IPv4, IPv6, 
+\emph on 
+AF_LOCAL
+\emph default 
+ and a pool of processes.
+ This library calls application-specific callbacks, which in turn, delegate
+ to JavaScript functions, allowing the application to be written into JS.
+ The tasks which were considered to be best written in C for efficiency
+ were exported as JS objects for use by the js-appserv application's scripts.
+\layout Standard
+
+The parent process initializes a JS runtime and context, loads in the main
+ application script (
+\emph on 
+main.js
+\emph default 
+) and executes it to create the JS functions.
+ This context is inherited (
+\emph on 
+MAP_COPY
+\emph default 
+) to the children processes.
+ This means that multiple connections within a process can be served under
+ the same context, although because of the nature of SpiderMonkey, which
+ includes a good Garbage Collector, and the fact that between connections
+ we clear 
+\emph on 
+SID
+\emph default 
+ and 
+\emph on 
+DATA
+\emph default 
+, this should generally not be a problem, and it increases efficiency.
+
+\emph on 
+ However, hooks are provided to the scripts so that they may load and cache
+ scripts and then execute them under different contexts as needed (XXX to
+ be implemented).
+\layout Subsubsection
+
+JS-AppServ Web applications
+\layout Standard
+
+Once in the 
+\emph on 
+Request Server State
+\emph default 
+ (
+\begin_inset LatexCommand \ref{sub:The-request-server}
+
+\end_inset 
+
+), the non-system requests are forwarded to JS functions provided by the
+ custom user Web application.
+\emph on 
+This system may dispatch operations under various contexts as necessary.
+\layout Standard
+
+XXX
+\layout Section
+
+The JS-AppServ protocol (JSASP)
+\layout Standard
+
+Here we define the JSASP.
+\layout Subsection
+
+Overview
+\layout Standard
+
+The use of the 
+\noun on 
+JSON
+\noun default 
+ (
+\noun on 
+JavaScript Object Notation
+\noun default 
+) format was chosen for its simplicity and efficiency, using a simple custom
+ protocol, over other approaches often using XML and HTTP, or complex proprietar
+y binary formats.
+ A custom JSON parser was implemented in order to avoid needing to use the
+ insecure 
+\emph on 
+eval()
+\emph default 
+ function.
+\layout Standard
+
+The JSASP allows the applications to run independently from the HTTP daemon
+ and to be distributed as necessary among various servers as application
+ load requirements increase.
+ It permits the HTTPd to create and maintain sessions and perform requests
+ on behalf of a session to serve a user.
+\layout Standard
+
+These session-specific requests, similarily as with FastCGI, can be classified
+ under three roles, 
+\emph on 
+Responder
+\emph default 
+ (serving 
+\emph on 
+GET
+\emph default 
+, 
+\emph on 
+POST
+\emph default 
+, 
+\emph on 
+PUT
+\emph default 
+), 
+\emph on 
+Authorizer
+\emph default 
+ (allowing or not access to a particular resource), and 
+\emph on 
+Filter
+\emph default 
+ (allowing to transparently filter/process files).
+\layout Subsection
+
+Stateless commands
+\layout Standard
+
+These commands are always available independently of the current connection
+ state.
+\layout Subsubsection
+
+ping
+\layout Itemize
+
+Request:
+\layout LyX-Code
+
+({cmd:
+\begin_inset Quotes erd
+\end_inset 
+
+ping
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Itemize
+
+Response:
+\layout LyX-Code
+
+({result:true, msg:
+\begin_inset Quotes erd
+\end_inset 
+
+pong
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Standard
+
+The main purpose of this command is to either verify if the connection still
+ works properly, and/or to prevent the connection from being closed by js-appser
+v for inactivity timeout.
+ Oftentimes the HTTPd will simply let the connection timeout automatically,
+ which allows to free processes serving very idle sessions to serve more
+ busy sessions.
+\layout Subsubsection
+
+
+\begin_inset LatexCommand \label{sub:quit}
+
+\end_inset 
+
+quit
+\layout Itemize
+
+Request:
+\layout LyX-Code
+
+({cmd:
+\begin_inset Quotes erd
+\end_inset 
+
+quit
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Itemize
+
+Response:
+\layout LyX-Code
+
+({result:true, msg:
+\begin_inset Quotes erd
+\end_inset 
+
+<message>
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Standard
+
+This command may be issued to immediately interrupt the connection at any
+ time.
+ If currently serving a SID, the session-specific data will be saved so
+ that the session may be resumed.
+\layout Subsection
+
+The authentication state
+\layout Standard
+
+Every httpd->js-appserv connection starts in the greeting state.
+\layout Standard
+
+
+\emph on 
+XXX
+\layout Subsection
+
+The SID management state
+\layout Standard
+
+After successful authentication, the HTTPd can request to resume a previous
+ valid SID, or request that a new SID be created.
+ It is also possible to verify for validity of a SID and then request a
+ new one if the requested one did not exist, in which case the newly created
+ SID should be sent again as session cookie to the user by the HTTPd.
+\layout Standard
+
+Care is made so that once activated on a connection, no other connection
+ can activate the same SID, among other processes of the same js-appserv
+ or between multiple local or remote ones.
+ For this reason, SIDs are often stored into a common database (postgresql
+ in the case of the reference implementation).
+\layout Standard
+
+However, if it is known that no conflict can occur (only a single js-appserv
+ being run for instance, and that shared memory allows the various connections
+ to share an in-memory hash table), an implementation could potentially
+ use an in-memory system instead of depending on an RDBMS.
+\layout Standard
+
+This state begins with the message:
+\layout LyX-Code
+
+({result:true, msg:"Ready"})
+\layout Subsubsection
+
+sid_load
+\layout Itemize
+
+Request:
+\layout LyX-Code
+
+({cmd:
+\begin_inset Quotes erd
+\end_inset 
+
+sid_load
+\begin_inset Quotes erd
+\end_inset 
+
+, sid:
+\begin_inset Quotes erd
+\end_inset 
+
+<sid>
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Itemize
+
+Response:
+\layout LyX-Code
+
+({result:<boolean>, msg:
+\begin_inset Quotes erd
+\end_inset 
+
+<string>
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Standard
+
+Request to resume serving a specific session.
+ This occurs after unexpected connection loss between the HTTPd and the
+ js-appserv application, or after disconnection due to request inactivity
+ timeout for the SID, where the closing phase saved persistent state information
+ for it.
+\layout Standard
+
+If successful, this request causes the persistent data to be loaded back
+ and the specified SID to become the active one for this connection.
+ This request can only succeed if not currently serving a SID.
+\layout Standard
+
+On error, 
+\emph on 
+result
+\emph default 
+ will be 
+\emph on 
+false
+\emph default 
+ in the response, but the connection is left open for the HTTPd to optionally
+ request creation of a new SID with the 
+\emph on 
+sid_create
+\emph default 
+ (
+\begin_inset LatexCommand \ref{sub:sid_create}
+
+\end_inset 
+
+) command, although it may also issue the 
+\emph on 
+quit
+\emph default 
+ (
+\begin_inset LatexCommand \ref{sub:quit}
+
+\end_inset 
+
+) command.
+\layout Subsubsection
+
+
+\begin_inset LatexCommand \label{sub:sid_create}
+
+\end_inset 
+
+sid_create
+\layout Itemize
+
+Request:
+\layout LyX-Code
+
+({cmd:
+\begin_inset Quotes erd
+\end_inset 
+
+sid_create
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Itemize
+
+Response:
+\layout LyX-Code
+
+{{result:<boolean>, msg:
+\begin_inset Quotes erd
+\end_inset 
+
+<string>
+\begin_inset Quotes erd
+\end_inset 
+
+[, sid:
+\begin_inset Quotes erd
+\end_inset 
+
+<sid>
+\begin_inset Quotes erd
+\end_inset 
+
+]})
+\layout Standard
+
+Requests that a new session be created.
+ This is only possible if not currently serving a SID.
+ When successful, the SID is returned and becomes the currently active SID
+ being served by this connection.
+\layout Standard
+
+On error, 
+\emph on 
+result
+\emph default 
+ is 
+\emph on 
+false
+\emph default 
+ but the connection is left open.
+ Failure here consists of an abnormal application error.
+\layout Subsubsection
+
+sid_destroy
+\layout Itemize
+
+Request:
+\layout LyX-Code
+
+({cmd:
+\begin_inset Quotes erd
+\end_inset 
+
+sid_destroy
+\begin_inset Quotes erd
+\end_inset 
+
+, sid:
+\begin_inset Quotes erd
+\end_inset 
+
+<sid>
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Itemize
+
+Response:
+\layout LyX-Code
+
+({result:<boolean>, msg:
+\begin_inset Quotes erd
+\end_inset 
+
+<string>
+\begin_inset Quotes erd
+\end_inset 
+
+})
+\layout Standard
+
+Requests that the currently being served session be destroyed.
+ This is only possible if currently serving that particular SID.
+ In general, SID expiration or the application will decide when to destroy
+ the SID.
+ This however is provided for convenience where the request could be initiated
+ via the HTTPd.
+ The connection is immediately closed after the SID is destroyed.
+\layout Subsection
+
+
+\begin_inset LatexCommand \label{sub:The-request-server}
+
+\end_inset 
+
+The request server state
+\layout Standard
+
+In this state the application can serve various requests for a specific
+ SID until it expires or a request inactivity timeout occurs, causing a
+ switch to 
+\emph on 
+The Closing State
+\emph default 
+ (
+\begin_inset LatexCommand \ref{sub:The-closing-state}
+
+\end_inset 
+
+).
+ These requests are only possible if currently serving a valid, unexpired
+ SID.
+ Multiple such requests are processed over the same persistent connection.
+\layout Subsubsection
+
+responder_get
+\layout Standard
+
+
+\emph on 
+XXX
+\layout Subsubsection
+
+responder_post
+\layout Standard
+
+
+\emph on 
+XXX
+\layout Subsubsection
+
+responder_put
+\layout Standard
+
+
+\emph on 
+XXX
+\layout Subsubsection
+
+authorizer_*
+\layout Standard
+
+
+\emph on 
+XXX
+\layout Subsubsection
+
+filter_*
+\layout Standard
+
+
+\emph on 
+XXX Might not need SID?
+\layout Subsection
+
+
+\begin_inset LatexCommand \label{sub:The-closing-state}
+
+\end_inset 
+
+The closing state
+\layout Standard
+
+If the SID is not yet to be discarded, the application saves any persistent
+ data for the SID before closing, so that resuming requests for it under
+ a future connection be possible.
+ The connection is then closed.
+ This state information includes the 
+\emph on 
+DATA
+\emph default 
+ object which is automatically hanled, as well as optional application specific
+ data which might need saving (the application can provide a function to
+ be called at this effect).
+\layout Section
+
+The Web Application Framework
+\layout Standard
+
+
+\emph on 
+XXX
+\the_end
diff --git a/mmsoftware/js/js-appserv/src/GNUmakefile b/mmsoftware/js/js-appserv/src/GNUmakefile
new file mode 100644 (file)
index 0000000..3aa9f8d
--- /dev/null
@@ -0,0 +1,39 @@
+# $Id: GNUmakefile,v 1.1 2006/08/20 06:59:00 mmondor Exp $
+
+CFLAGS += -Wall -DDEBUG
+
+JS_CFLAGS := $(shell spidermonkey-config -dc)
+JS_LDFLAGS := $(shell spidermonkey-config -dl)
+
+PG_CFLAGS := $(shell pg_config --cppflags)
+PG_CFLAGS += -I$(shell pg_config --includedir)
+PG_LDFLAGS := $(shell pg_config --ldflags)
+PG_LDFLAGS += -lpq
+
+
+MMOBJS := $(addprefix ../../../mmlib/,mmpool.o mmlog.o mmreadcfg.o \
+       mmstring.o mmhash.o mmalarm.o mmheap.o mmlimitrate.o mmserver2.o)
+JSOBJS := $(addprefix ../../classes/,js_errno.o js_fd.o js_pgsql.o)
+
+
+CFLAGS += $(JS_CFLAGS) $(PG_CFLAGS) -I. -I../../../mmlib -I../../classes
+LDFLAGS += $(JS_LDFLAGS) $(PG_LDFLAGS)
+
+OBJ := js-appserv.o
+
+
+all: js-appserv
+
+%.o: %.c
+       cc -c $(CFLAGS) -o $@ $<
+
+js-appserv: $(OBJ) $(MMOBJS) $(JSOBJS)
+       cc -o $@ $(OBJ) $(LDFLAGS) -lc $(MMOBJS) $(JSOBJS)
+
+install: all
+       install -cs -o 0 -g 0 -m 500 js-appserv /usr/local/sbin
+       install -c -o 0 -g 0 -m 444 js-appserv.conf.5 /usr/local/man/man5
+       install -c -o 0 -g 0 -m 444 js-appserv.8 /usr/local/man/man8
+
+clean:
+       rm -f js-appserv $(OBJ) $(MMOBJS) $(JSOBJS)
diff --git a/mmsoftware/js/js-appserv/src/js-appserv.8 b/mmsoftware/js/js-appserv/src/js-appserv.8
new file mode 100644 (file)
index 0000000..844f954
--- /dev/null
@@ -0,0 +1,160 @@
+.\" $Id: js-appserv.8,v 1.1 2006/08/20 06:59:00 mmondor Exp $
+.\"
+.\" Copyright (C) 2006, Matthew Mondor
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"      This product includes software written by Matthew Mondor.
+.\" 4. The name of Matthew Mondor may not be used to endorse or promote
+.\"    products derived from this software without specific prior written
+.\"    permission.
+.\" 5. Redistribution of source code may not be released under the terms of
+.\"    any GNU Public License derivate.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+.\" IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 24, 2006
+.Dt JS-CGID 8
+.Os mmsoftware
+.Sh NAME
+.Nm js-appserv
+.Nd
+.Xr inetd 8
+alternative for better security
+.Sh SYNOPSIS
+.Nm js-appserv Op Ar config_file
+.Sh DESCRIPTION
+.Nm
+is a useful replacement for
+.Xr inetd 8
+when security is a concern. It only allows to serve one service per running
+instance, but a different configuration file can be provided for each service
+to run several ones as required. It supports connection number and rate limits
+on a global and per-address basis,
+.Xr chroot 2
+and
+.Xr setrlimit 2 . It will only
+.Xr bind 2
+to specified addresses/interfaces to listen for connections.
+It also ensures to drop privileges definitely before starting operation, using
+.Xr setgid 2 ,
+.Xr setuid 2
+and
+.Xr setgroups 2 .
+.Pp
+Further, it comports a safe
+.Xr execve 2
+wrapper which ensures to not pass unexpected arguments or environmental
+variables, as well as to only execute ELF binaries which are owned by the
+superuser and non-writable. No globbing whatsoever is performed by it,
+and it only allows an absolute path to be provided to the application to
+launch. It will not allow execution of files with the setuid or setgid
+bits set.
+.Pp
+All connections are logged via the specified facility using
+.Xr syslog 3 ,
+as well as the exit code of the application when closing the connection with
+the client. Any security issue encoutered by the
+.Xr execve 2
+wrapper is also logged.
+.Pp
+It's default configuration makes it harmless but useless. See the
+.Xr js-appserv.conf 5
+manual page for configuration file description.
+.Pp
+A good usage for
+.Xr js-appserv 8
+is for instance to run a CVS pserver in safe conditions. It then can be
+restrited to the specified root directory, and access the files with read-only
+access under an unprivileged user. If used for this purpose, also look at
+the
+.Xr mmanoncvs 8
+manual page.
+.Pp
+Another interesting feature is being able to specify if the currently running
+clients should be interrupted or not if the
+.Nm
+server is to be stopped or restarted. In the case of a CVS checkout for
+instance, if
+.Nm KILL_CHILDREN
+configuration option is FALSE, the operation will continue until it finishes
+normally, even in the event of a server configuration change or restart.
+.Pp
+For additional security, only the superuser is allowed to launch
+.Nm ,
+unless compiled with the
+.Nm -DNODROPPRIVS
+option, in which case it could be used by a nonprivileged user to bind
+a service to a high port, and server refuse to be launched by root.
+Other users attempting to launch the service will cause a line to be logged,
+telling which user ID attempted the launch.
+.Pp
+When launched successfully, a status line is logged telling under which 
+user ID it runs, which port it listens to and on which addresses/interfaces,
+as well as the configuration file location and service command.
+.Pp
+Note that this system redirects the stderr stream (filedescriptor 2) of
+the command launched to serve the clients to the "/dev/null" device, so that
+errors output from it be not disclosed to the remote user.
+.Pp
+Another optional interesting feature which it offers is the possibility
+to use advisory locking on a special control file to synchronize the service
+with another program. The lock is acquired in exclusive mode by
+.Nm
+when at least one connection exists being served. It is released when no
+more connections remain. This way, another program can rely on the fact that
+noone is using the service if it can obtain the lock. As long as the other
+application holds the lock, the service will refuse new connections. Normal
+service resumes immediately as the lock is released by the third party
+application. Of course, it is important to properly configure the permissions
+on that lock file, if the feature is enabled.
+.Pp
+Eventual support for bandwidth shaping/throttling and network inactivity
+timeout limits are planned. However, as those will require a redesign and
+rewrite of
+.Nm ,
+all we support for now is total maximum timeout for execution of the supplied
+service command. This is still better than
+.Xr inetd 8
+behavior, however.
+.Sh FILES
+.Bl -tag -width indent -offset indent
+.It Pa /usr/local/sbin/js-appserv
+The actual server binary
+.It Pa /usr/local/etc/js-appserv.conf
+The default configuration file to read
+.El
+.Sh AUTHOR
+.Nm
+was written by Matthew Mondor, and is
+Copyright (c) 2006, Matthew Mondor, All Rights Reserved.
+.Sh SEE ALSO
+.Xr js-appserv.conf 5 ,
+.Xr bind 2 ,
+.Xr chroot 2 ,
+.Xr setrlimit 2 ,
+.Xr setuid 2 ,
+.Xr setgid 2 ,
+.Xr setgroups 2 ,
+.Xr mmanoncvs 8 ,
+.Xr inetd 8 .
+.Sh BUGS
+Please report any bug to mmondor@accela.net
diff --git a/mmsoftware/js/js-appserv/src/js-appserv.c b/mmsoftware/js/js-appserv/src/js-appserv.c
new file mode 100644 (file)
index 0000000..9a979c2
--- /dev/null
@@ -0,0 +1,1030 @@
+/* $Id: js-appserv.c,v 1.1 2006/08/20 06:59:00 mmondor Exp $ */
+
+/*
+ * Copyright (C) 2006, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by Matthew Mondor.
+ * 4. The name of Matthew Mondor may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ * 5. Redistribution of source code may not be released under the terms of
+ *    any GNU Public License derivate.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * TODO:
+ * - Create the request structure object only once per request for performance
+ * - Support option to allow to send the stderr output of launched processes
+ *   to a file for debugging. This however requires support in mmserver2(3)
+ *   for this.  This would only be for debugging, especially because of the
+ *   fact that multiple processes could attempt to append at the end of the
+ *   same file at once, concurrently. We can't really use a lock either, since
+ *   we won't be controlling the output, unless we used a popen(3)-like
+ *   system.
+ * - Provide an enumeration for the reject handler reason parameter.
+ * - Provide a few utility functions to the application, such as context
+ *   creation and script execution, setting up timers, etc.
+ * - Possibly let the application specify a setsockopt function.
+ * - Perhaps create the JS contexts in the children processes between
+ *   every connection in order to avoid any possible memory leaks.
+ *   This of course would have a performance impact.  Interestingly, it is
+ *   possible to not be necessary because of the GC, and moreover because
+ *   we can recycle a process after serving a number of requests.
+ */
+
+
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>            /* strerror(3) */
+#include <syslog.h>
+#include <unistd.h>
+
+#include <jsapi.h>
+
+#include <mmtypes.h>
+#include <mmreadcfg.h>
+#include <mmstring.h>
+#include <mmserver2.h>
+
+#include <js_errno.h>
+#include <js_fd.h>
+#include <js_pgsql.h>
+
+
+
+/* DEFINITIONS */
+
+struct config {
+       char SCRIPT_PATH[256], PROCTITLE[256], SYSLOG_FACILITY[32],
+           PID_PATH[256], LOCK_PATH[256], SHLOCKS_PATH[256], CHROOT_DIR[256],
+           USER[32], GROUPS[64], LISTEN_TO[256];
+       long CHILDREN_INITIAL, CHILDREN_MINSPARE, CHILDREN_MAXSPARE,
+           CHILDREN_MAXIMUM, CHILDREN_MAXIMUM_REQUESTS,
+           CHILDREN_AVERAGE_SECONDS, LISTEN_BACKLOG,
+           ADDRESS_CACHE_SIZE, ADDRESS_CACHE_RATE_LIMIT,
+           ADDRESS_CACHE_RATE_PERIOD, ADDRESS_CACHE_CONCURRENCY_LIMIT,
+           LIMIT_CORE, LIMIT_CPU, LIMIT_DATA, LIMIT_FSIZE, LIMIT_MEMLOCK,
+           LIMIT_NOFILE, LIMIT_NPROC, LIMIT_RSS, LIMIT_STACK;
+       bool USING_EXECVE, DROP_PRIVILEGES, EXIT_KILL_CHILDREN,
+           CHILDREN_SETSID, ADDRESS_RESOLVE, ADDRESS_RATE_LIMITS,
+           ADDRESS_CONCURRENCY_LIMITS, RLIMITS, SIGHUP_INTERRUPT_CHILDREN,
+           JS_GC_SIZE, JS_STACK_SIZE;
+};
+
+
+
+/* PROTOTYPES */
+
+int                    main(int, char **);
+
+static int             setsockopts(int);
+static int             config_read(const char *, uid_t *, gid_t **, int *);
+static int             lock_check(const char *);
+static int             listen_to(const char *);
+static int             rlimit_security(void);
+static JSBool          branch_callback(JSContext *, JSScript *);
+static void            context_error_reporter(JSContext *, const char *,
+                           JSErrorReport *);
+static JSContext       *context_create(JSRuntime *, size_t, JSObject **);
+static int             script_reload(const char *);
+static int             parent_init_hook(void);
+static void            parent_exit_hook(void);
+static void            parent_sighup_hook(void);
+static int             child_init_hook(void);
+static void            child_exit_hook(void);
+static void            child_sigalrm_hook(void);
+static JSObject                *server_request_object(struct server_request *);
+static void            server_request_object_destroy(void);
+static void            request_handler(struct server_request *);
+static void            reject_handler(struct server_request *, int);
+static void            request_close_hook(struct server_request *);
+static void            request_interrupt_hook(struct server_request *);
+
+
+
+/* GLOBALS */
+
+static struct config   CONF;
+static char            daemon_title[64];
+
+/* For mmreadcfg() */
+static carg_t  cargs[] = {
+       {CAT_STR, CAF_NONE, 1, 255, "SCRIPT_PATH", CONF.SCRIPT_PATH},
+       {CAT_STR, CAF_NONE, 1, 63, "PROCTITLE", CONF.PROCTITLE},
+       {CAT_STR, CAF_NONE, 1, 31, "SYSLOG_FACILITY", CONF.SYSLOG_FACILITY},
+       {CAT_STR, CAF_NONE, 1, 255, "PID_PATH", CONF.PID_PATH},
+       {CAT_STR, CAF_NONE, 1, 255, "LOCK_PATH", CONF.LOCK_PATH},
+       {CAT_STR, CAF_NONE, 1, 255, "SHLOCKS_PATH", CONF.SHLOCKS_PATH},
+       {CAT_STR, CAF_NONE, 1, 255, "CHROOT_DIR", CONF.CHROOT_DIR},
+       {CAT_STR, CAF_NONE, 1, 31, "USER", CONF.USER},
+       {CAT_STR, CAF_NONE, 1, 63, "GROUPS", CONF.GROUPS},
+       {CAT_STR, CAF_NONE, 1, 255, "LISTEN_TO", CONF.LISTEN_TO},
+       {CAT_VAL, CAF_NONE, 1, 1024, "CHILDREN_INITIAL",
+               &CONF.CHILDREN_INITIAL},
+       {CAT_VAL, CAF_NONE, 1, 1024, "CHILDREN_MINSPARE",
+               &CONF.CHILDREN_MINSPARE},
+       {CAT_VAL, CAF_NONE, 1, 1024, "CHILDREN_MAXSPARE",
+               &CONF.CHILDREN_MAXSPARE},
+       {CAT_VAL, CAF_NONE, 1, 1024, "CHILDREN_MAXIMUM",
+               &CONF.CHILDREN_MAXIMUM},
+       {CAT_VAL, CAF_NONE, 1, 1024, "CHILDREN_MAXIMUM_REQUESTS",
+               &CONF.CHILDREN_MAXIMUM_REQUESTS},
+       {CAT_VAL, CAF_NONE, 1, 1024, "CHILDREN_AVERAGE_SECONDS",
+               &CONF.CHILDREN_AVERAGE_SECONDS},
+       {CAT_VAL, CAF_NONE, 0, 99999, "LISTEN_BACKLOG", &CONF.LISTEN_BACKLOG},
+       {CAT_VAL, CAF_NONE, 0, 4096, "ADDRESS_CACHE_SIZE",
+               &CONF.ADDRESS_CACHE_SIZE},
+       {CAT_VAL, CAF_NONE, 0, 4096, "ADDRESS_CACHE_RATE_LIMIT",
+               &CONF.ADDRESS_CACHE_RATE_LIMIT},
+       {CAT_VAL, CAF_NONE, 0, 1800, "ADDRESS_CACHE_RATE_PERIOD",
+               &CONF.ADDRESS_CACHE_RATE_PERIOD},
+       {CAT_VAL, CAF_NONE, 0, 1024, "ADDRESS_CACHE_CONCURRENCY_LIMIT",
+               &CONF.ADDRESS_CACHE_CONCURRENCY_LIMIT},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_CORE", &CONF.LIMIT_CORE},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_CPU", &CONF.LIMIT_CPU},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_DATA", &CONF.LIMIT_DATA},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_FSIZE", &CONF.LIMIT_FSIZE},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_MEMLOCK", &CONF.LIMIT_MEMLOCK},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_NOFILE", &CONF.LIMIT_NOFILE},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_NPROC", &CONF.LIMIT_NPROC},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_RSS", &CONF.LIMIT_RSS},
+        {CAT_VAL, CAF_NONE, 0, 0, "RLIMIT_STACK", &CONF.LIMIT_STACK},
+        {CAT_VAL, CAF_NONE, 0, 0, "JS_GC_SIZE", &CONF.JS_GC_SIZE},
+        {CAT_VAL, CAF_NONE, 0, 0, "JS_STACK_SIZE", &CONF.JS_STACK_SIZE},
+       {CAT_BOOL, CAF_NONE, 0, 0, "USING_EXECVE", &CONF.USING_EXECVE},
+       {CAT_BOOL, CAF_NONE, 0, 0, "DROP_PRIVILEGES", &CONF.DROP_PRIVILEGES},
+       {CAT_BOOL, CAF_NONE, 0, 0, "EXIT_KILL_CHILDREN",
+               &CONF.EXIT_KILL_CHILDREN},
+       {CAT_BOOL, CAF_NONE, 0, 0, "CHILDREN_SETSID", &CONF.CHILDREN_SETSID},
+       {CAT_BOOL, CAF_NONE, 0, 0, "ADDRESS_RESOLVE", &CONF.ADDRESS_RESOLVE},
+       {CAT_BOOL, CAF_NONE, 0, 0, "ADDRESS_RATE_LIMITS",
+               &CONF.ADDRESS_RATE_LIMITS},
+       {CAT_BOOL, CAF_NONE, 0, 0, "ADDRESS_CONCURRENCY_LIMITS",
+               &CONF.ADDRESS_CONCURRENCY_LIMITS},
+       {CAT_BOOL, CAF_NONE, 0, 0, "RLIMITS", &CONF.RLIMITS},
+       {CAT_BOOL, CAF_NONE, 0, 0, "SIGHUP_INTERRUPT_CHILDREN",
+               &CONF.SIGHUP_INTERRUPT_CHILDREN},
+       {CAT_END, CAF_NONE, 0, 0, NULL, NULL}
+};
+static cmap_t  cmap[] = {
+       {"LOG_AUTH", LOG_AUTH},
+       {"LOG_AUTHPRIV", LOG_AUTHPRIV},
+       {"LOG_CRON", LOG_CRON},
+       {"LOG_DAEMON", LOG_DAEMON},
+       {"LOG_FTP", LOG_FTP},
+       {"LOG_LPR", LOG_LPR},
+       {"LOG_MAIL", LOG_MAIL},
+       {"LOG_NEWS", LOG_NEWS},
+       {"LOG_SYSLOG", LOG_SYSLOG},
+       {"LOG_USER", LOG_USER},
+       {"LOG_UUCP", LOG_UUCP},
+       {NULL, 0}
+};
+
+static JSClass class_global = {
+       "global", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+       JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
+       JS_FinalizeStub
+};
+
+/*
+ * The JS runtime and context is global, inherited from the parent to the
+ * children processes.
+ */
+JSRuntime      *p_rt = NULL;
+JSContext      *p_ctx = NULL;
+JSObject       *p_obj = NULL;
+JSScript       *p_script = NULL;
+jsval          p_robj;
+
+/*
+ * And these are used by the children processes
+ */
+JSContext      *c_ctx;
+
+
+
+/* TEXT */
+
+int
+main(int argc, char **argv)
+{
+       struct server_config    c;
+       uid_t                   uid;
+       gid_t                   *gids;
+       int                     ngids, ch;
+       char                    *conf_file = "/usr/local/etc/js-appserv.conf";
+
+       /*
+        * Make sure that only the superuser may launch this daemon
+        */
+#ifdef SUPERUSER_ONLY
+       if (getuid() != 0) {
+               (void) fprintf(stderr,
+                   "Only can be started by the superuser\n");
+               syslog(LOG_NOTICE,
+                   "User %d attempted to launch js-appserv with '%s'",
+                   getuid(), conf_file);
+               exit(EXIT_FAILURE);
+       }
+#endif
+
+       /*
+        * Parse command line arguments
+        */
+       while ((ch = getopt(argc, argv, "f:")) != -1) {
+               switch (ch) {
+               case 'f':
+                       conf_file = optarg;
+                       break;
+               case '?':
+                       /* FALLTHROUGH */
+               default:
+                       (void) fprintf(stderr,
+                           "usage: js-appserv [-f <configfile>]\n");
+                       exit(EXIT_FAILURE);
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       /*
+        * Things we must do before calling chroot(2)
+        */
+
+       /* Read config file. This also calls openlog(3) for us */
+       if (config_read(conf_file, &uid, &gids, &ngids) == -1) {
+               (void) fprintf(stderr, "Error reading configuration file."
+                   " Verify syslog for more details.\n");
+               exit(EXIT_FAILURE);
+       }
+
+       /* Make sure that we're not already running */
+       if (lock_check(CONF.LOCK_PATH) == -1) {
+               (void) fprintf(stderr, "Already running!? - %s\n",
+                   strerror(errno));
+               syslog(LOG_NOTICE, "Already running!? - %s", strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+
+       /*
+        * Chroot if wanted. Note that server_start() will chdir(2), but since
+        * we can setup any files we may need, we chdir(2) here as well.
+        */
+       if (*CONF.CHROOT_DIR != '\0') {
+               if (chroot(CONF.CHROOT_DIR) == -1) {
+                       syslog(LOG_NOTICE, "chroot(%s) - %s", CONF.CHROOT_DIR,
+                           strerror(errno));
+                       exit(EXIT_FAILURE);
+               }
+               (void) chdir("/");
+       }
+
+       /*
+        * Bind network server ports.
+        */
+       server_init();
+       if (listen_to(CONF.LISTEN_TO) == -1)
+               exit(EXIT_FAILURE);
+
+       /*
+        * Finally drop privileges.
+        */
+       if ((CONF.DROP_PRIVILEGES && getuid() == 0) &&
+           !mmdropprivs(uid, gids, ngids)) {
+               syslog(LOG_NOTICE, "Error dropping privileges - %s",
+                   strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+
+       /*
+        * Initialize and start server. New user must be able to create lock
+        * files as necessary where specified in SHLOCKS_PATH configuration
+        * directive.
+        */
+       (void) mm_memclr(&c, sizeof(struct server_config));
+       (void) mm_strcpy(c.locks_path, CONF.SHLOCKS_PATH);
+       *c.locks_user = '\0';
+       *c.locks_group = '\0';
+       c.locks_mode = 0600;
+       (void) mm_strcpy(c.null_path, "/dev/null");
+       (void) mm_strcpy(c.pid_path, CONF.PID_PATH);
+       (void) mm_strncpy(c.proc_title, CONF.PROCTITLE, 31);
+       c.children_initial = CONF.CHILDREN_INITIAL;
+       c.children_minspare = CONF.CHILDREN_MINSPARE;
+       c.children_maxspare = CONF.CHILDREN_MAXSPARE;
+       c.children_maximum = CONF.CHILDREN_MAXIMUM;
+       c.children_average_seconds = CONF.CHILDREN_AVERAGE_SECONDS;
+       c.children_maxrequests = CONF.CHILDREN_MAXIMUM_REQUESTS;
+       c.serialization_lock = TRUE;
+       c.exit_interrupt_requests = CONF.EXIT_KILL_CHILDREN;
+       c.children_setsid = CONF.CHILDREN_SETSID;
+       c.using_execve = CONF.USING_EXECVE;
+       c.parent_init_hook = parent_init_hook;
+       c.parent_exit_hook = parent_exit_hook;
+       c.parent_sighup_hook = parent_sighup_hook;
+       c.parent_timer_hook = NULL;
+       c.parent_timer_seconds = 0;
+       c.child_init_hook = child_init_hook;
+       c.child_exit_hook = child_exit_hook;
+       c.child_sigalrm_hook = child_sigalrm_hook;
+       if (server_start(&c) == -1)
+               exit(EXIT_FAILURE);
+
+       /* NOTREACHED */
+       exit(EXIT_SUCCESS);
+}
+
+static int
+config_read(const char *file, uid_t *uid, gid_t **gids, int *ngids)
+{
+       cres_t  cres;
+       long    facility;
+
+       /*
+        * Set configuration defaults
+        */
+       (void) mm_strcpy(CONF.SCRIPT_PATH, "/app/main.js");
+       *CONF.PROCTITLE = '\0';
+       (void) mm_strcpy(CONF.SYSLOG_FACILITY, "LOG_DAEMON");
+       (void) mm_strcpy(CONF.PID_PATH, "/var/run/js-appserv.pid");
+       (void) mm_strcpy(CONF.LOCK_PATH, "/var/run/js-appserv-lock");
+       (void) mm_strcpy(CONF.SHLOCKS_PATH, "/var/run/js-appserv-lock");
+       *CONF.CHROOT_DIR = '\0';
+       (void) mm_strcpy(CONF.USER, "js-appserv");
+       (void) mm_strcpy(CONF.GROUPS, "js-appserv");
+       (void) mm_strcpy(CONF.LISTEN_TO, "127.0.0.1:2323");
+       CONF.CHILDREN_INITIAL = 5;
+       CONF.CHILDREN_MINSPARE = 5;
+       CONF.CHILDREN_MAXSPARE = 10;
+       CONF.CHILDREN_MAXIMUM = 64;
+       CONF.CHILDREN_MAXIMUM_REQUESTS = 5000;
+       CONF.CHILDREN_AVERAGE_SECONDS = 300;
+       CONF.LISTEN_BACKLOG = 256;
+       CONF.USING_EXECVE = FALSE;
+       CONF.DROP_PRIVILEGES = TRUE;
+       CONF.EXIT_KILL_CHILDREN = TRUE;
+       CONF.CHILDREN_SETSID = FALSE;
+       CONF.ADDRESS_RESOLVE = FALSE;
+       CONF.ADDRESS_RATE_LIMITS = FALSE;
+       CONF.ADDRESS_CONCURRENCY_LIMITS = FALSE;
+       CONF.ADDRESS_CACHE_SIZE = 0;
+       CONF.ADDRESS_CACHE_RATE_LIMIT = 0;
+       CONF.ADDRESS_CACHE_RATE_PERIOD = 0;
+       CONF.ADDRESS_CACHE_CONCURRENCY_LIMIT = 0;
+       CONF.RLIMITS = FALSE;
+       CONF.LIMIT_CORE = -1;
+       CONF.LIMIT_CPU = -1;
+       CONF.LIMIT_DATA = -1;
+       CONF.LIMIT_FSIZE = -1;
+       CONF.LIMIT_MEMLOCK = -1;
+       CONF.LIMIT_NOFILE = -1;
+       CONF.LIMIT_NPROC = -1;
+       CONF.LIMIT_RSS = -1;
+       CONF.LIMIT_STACK = -1;
+       CONF.JS_GC_SIZE = 1024;
+       CONF.JS_STACK_SIZE = 8;
+       CONF.SIGHUP_INTERRUPT_CHILDREN = FALSE;
+
+       /*
+        * Read and parse configuration file
+        */
+       if (!mmreadcfg(&cres, cargs, file)) {
+               syslog(LOG_NOTICE, "Cannot read configuration file '%s'",
+                   file);
+               return -1;
+       }
+
+       /*
+        * Setup syslog. First perform sanity checking on supplied syslog
+        * facility string.
+        */
+       if (!mmmapstring(cmap, CONF.SYSLOG_FACILITY, &facility)) {
+               syslog(LOG_NOTICE, "Invalid syslog facility '%s'",
+                   CONF.SYSLOG_FACILITY);
+               return -1;
+       }
+       if (*CONF.PROCTITLE != '\0')
+               (void) snprintf(daemon_title, 63, "js-appserv:%s",
+                   CONF.PROCTITLE);
+       else
+               (void) mm_strcpy(daemon_title, "js-appserv");
+       openlog(daemon_title, LOG_PID | LOG_NDELAY, facility);
+
+       /*
+        * Now do some sanity checking on supplied users and groups, we'll
+        * return those properly if they are valid.
+        */
+       if ((*uid = mmgetuid(CONF.USER)) == -1) {
+               syslog(LOG_NOTICE, "Unknown user '%s'", CONF.USER);
+               return -1;
+       }
+       if (!(*gids = mmgetgidarray(ngids, CONF.GROUPS))) {
+               syslog(LOG_NOTICE, "At least one unknown group in '%s'",
+                   CONF.GROUPS);
+               return -1;
+       }
+
+       /* Everything successful */
+       return 0;
+}
+
+/*
+ * Function to verify if we are already running. Every configuration should
+ * provide a lock file path to do this check.
+ */
+static int
+lock_check(const char *file)
+{
+       int fd;
+
+       if ((fd = open(file, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1) {
+               if (flock(fd, LOCK_EX | LOCK_NB) == 0)
+                       return 0;
+               close(fd);
+       }
+
+       return -1;
+}
+
+/*
+ * Set wanted setsockopt(2) on sockets. This function is called internally for
+ * each socket we create using server_socket_bind().
+ */
+static int
+setsockopts(int fd)
+{
+       struct linger   l;
+
+#define SETSOCKOPT(l, o)       do {                                    \
+       int     _o = 1;                                                 \
+       if ((setsockopt(fd, (l), (o), &_o, sizeof(int))) == -1)         \
+               return -1;                                              \
+} while (/* CONSTCOND */0)
+
+       SETSOCKOPT(SOL_SOCKET, SO_REUSEADDR);
+       SETSOCKOPT(SOL_SOCKET, SO_REUSEPORT);
+       SETSOCKOPT(SOL_SOCKET, SO_KEEPALIVE);
+
+#undef SETSOCKOPT
+
+       l.l_onoff = 0;
+       l.l_linger = 0;
+       if ((setsockopt(fd, SOL_SOCKET, SO_LINGER, &l,
+                       sizeof(struct linger))) == -1)
+               return -1;
+
+       /* XXX Add TCP_NODELAY */
+
+       return 0;
+}
+
+/*
+ * Starts listening to all specified address:port pairs. Returns 0 on
+ * success, or -1 on error, logging the error via syslog(3).
+ */
+static int
+listen_to(const char *addresses)
+{
+       char    *string, *tstring;
+       char    *entries[32], *cols[3];
+       int     i, nentries, err;
+
+       string = tstring = NULL;
+       err = 0;
+
+       if ((string = _mm_strdup(addresses)) == NULL) {
+               syslog(LOG_NOTICE, "listen_to() - Out of memory error");
+               return -1;
+       }
+
+       if ((nentries = mm_straspl(entries, string, 31)) < 1) {
+               syslog(LOG_NOTICE, "No <address>:<port> specified in "
+                   "LISTEN_TO configuration directive");
+               err = -1;
+               goto end;
+       }
+
+       for (i = 0; i < nentries; i++) {
+               struct server_socket_config     sc;
+
+               tstring = _mm_strdup(entries[i]);
+               if (mm_strspl(cols, entries[i], 2, ':') != 2) {
+                       syslog(LOG_NOTICE,
+                           "[%s] not a valid <address>:<port> entry in "
+                           "LISTEN_TO configuration directive", tstring);
+                       err = -1;
+                       goto end;
+               }
+
+               (void) mm_memclr(&sc, sizeof(struct server_socket_config));
+               sc.family = (mm_strchr(cols[0], ':') != NULL ?
+                   AF_INET6 : AF_INET);
+               sc.type = SOCK_STREAM;
+               sc.port = (int)strtol(cols[1], NULL, 10);
+               sc.backlog = CONF.LISTEN_BACKLOG;
+               sc.create_stream = FALSE;
+               sc.address_resolve = CONF.ADDRESS_RESOLVE;
+               sc.address_rate_limits = CONF.ADDRESS_RATE_LIMITS;
+               sc.address_concurrency_limits =
+                   CONF.ADDRESS_CONCURRENCY_LIMITS;
+               sc.address_cache_size = CONF.ADDRESS_CACHE_SIZE;
+               sc.address_cache_rate_limit = CONF.ADDRESS_CACHE_RATE_LIMIT;
+               sc.address_cache_rate_period = CONF.ADDRESS_CACHE_RATE_PERIOD;
+               sc.address_cache_concurrency_limit =
+                   CONF.ADDRESS_CACHE_CONCURRENCY_LIMIT;
+               sc.packet_size = 0;
+               (void) mm_strcpy(sc.bind_address, cols[0]);
+               *sc.socket_user = '\0';
+               *sc.socket_group = '\0';
+               sc.socket_mode = 0;
+               sc.setsockopts = setsockopts;
+               sc.request_handler = request_handler;
+               sc.reject_handler = reject_handler;
+               sc.request_interrupt_hook = request_interrupt_hook;
+               sc.request_close_hook = request_close_hook;
+               sc.user_data = NULL;
+               if (server_socket_bind(&sc) == -1)
+                       syslog(LOG_NOTICE, "Error binding socket for [%s]",
+                           tstring);
+
+               free(tstring);
+               tstring = NULL;
+       }
+
+end:
+       if (string != NULL)
+               free(string);
+       if (tstring != NULL)
+               free(tstring);
+
+       return err;
+}
+
+static int
+rlimit_security(void)
+{
+       struct rlimit   rl;
+
+/*
+ * Use some preprocessor magic to shorten and clean up repetitive code:
+ */
+
+#define SETRLIMIT(restxt, resource, limit) do {                                \
+       if ((limit) != -1) {                                            \
+               rl.rlim_cur = rl.rlim_max = (rlim_t)(limit);            \
+               if (setrlimit((resource), &rl) == -1) {                 \
+                       syslog(LOG_NOTICE,                              \
+                           "* setrlimit(R%s, %ld) - (%s)",             \
+                           (restxt), (limit), strerror(errno));        \
+                       return -1;                                      \
+               }                                                       \
+       }                                                               \
+} while (/* CONSTCOND */0)
+
+#define SETRLIMIT2(n)  SETRLIMIT(#n, R##n, CONF.n)
+
+       SETRLIMIT2(LIMIT_CORE);
+       SETRLIMIT2(LIMIT_CPU);
+       SETRLIMIT2(LIMIT_DATA);
+       SETRLIMIT2(LIMIT_FSIZE);
+       SETRLIMIT2(LIMIT_MEMLOCK);
+       SETRLIMIT2(LIMIT_NOFILE);
+       SETRLIMIT2(LIMIT_NPROC);
+       SETRLIMIT2(LIMIT_RSS);
+       SETRLIMIT2(LIMIT_STACK);
+
+#undef SETRLIMIT2
+#undef SETRLIMIT
+
+       return 0;
+}
+
+static JSBool
+branch_callback(JSContext *ctx, JSScript *s)
+{
+       static int      count = 0;
+
+       if ((++count & 0x3fff) == 1)
+               JS_MaybeGC(ctx);
+
+       return JS_TRUE;
+}
+
+static void
+context_error_reporter(JSContext *cx, const char *msg, JSErrorReport *rep)
+{
+       syslog(LOG_NOTICE, "context_error_reporter() - %s : %s",
+           msg, rep->linebuf);
+}
+
+static JSContext *
+context_create(JSRuntime *rt, size_t stacksize, JSObject **obj)
+{
+       JSContext       *ctx;
+
+       if ((ctx = JS_NewContext(rt, stacksize)) == NULL) {
+               syslog(LOG_NOTICE, "context_create() - JS_NewContext()");
+               goto err;
+       }
+       (void) JS_SetErrorReporter(ctx, context_error_reporter);
+       if ((*obj = JS_NewObject(ctx, &class_global, NULL, NULL)) == NULL) {
+               syslog(LOG_NOTICE, "context_create() - JS_NewObject()");
+               goto err;
+       }
+       if (!JS_InitStandardClasses(ctx, *obj)) {
+               syslog(LOG_NOTICE,
+                   "context_create() - JS_InitStandardClasses()");
+               goto err;
+       }
+
+       /* Wanted custom classes */
+       if (!js_InitErrnoClass(ctx, *obj)) {
+               syslog(LOG_NOTICE, "context_create() - js_InitErrnoClass()");
+               goto err;
+       }
+       if (!js_InitFDClass(ctx, *obj)) {
+               syslog(LOG_NOTICE, "context_create() - js_InitFDClass()");
+               goto err;
+       }
+       if (!js_InitPGClass(ctx, *obj)) {
+               syslog(LOG_NOTICE, "context_create() - js_InitPGClass()");
+       }
+
+       /* Set our GC handler callback */
+       (void) JS_SetBranchCallback(ctx, branch_callback);
+
+       return ctx;
+
+err:
+       if (ctx)
+               JS_DestroyContext(ctx);
+
+       return NULL;
+}
+
+/*
+ * Attempt to load, preprocess and compile the supplied script.
+ * This also sets up the runtime environment and the classes.
+ * Occurs in the parent process, but before the children are started/recycled.
+ * On failure, we return -1 and we leave the previously compiled script
+ * as-is.  On success, any previously compiled script is freed and we
+ * return 0.
+ * If called with file == NULL, only frees any allocated script.
+ */
+static int
+script_reload(const char *file)
+{
+       JSRuntime       *rt = NULL;
+       JSContext       *ctx = NULL;
+       JSObject        *obj, *robj;
+       JSScript        *script = NULL;
+       jsval           val;
+
+       if (file == NULL) {
+               if (p_script != NULL && p_ctx != NULL) {
+                       JS_DestroyScript(p_ctx, p_script);
+                       p_script = NULL;
+               }
+               if (p_ctx != NULL) {
+                       (void) JS_RemoveRoot(p_ctx, &p_robj);
+                       JS_DestroyContext(p_ctx);
+                       p_ctx = NULL;
+               }
+               if (p_rt != NULL) {
+                       JS_DestroyRuntime(p_rt);
+                       p_rt = NULL;
+               }
+
+               return 0;
+       }
+
+       if ((rt = JS_NewRuntime(CONF.JS_GC_SIZE * 1024)) == NULL) {
+               syslog(LOG_NOTICE, "script_reload() - JS_NewRuntime()");
+               goto err;
+       }
+       if ((ctx = context_create(rt, CONF.JS_STACK_SIZE * 1024, &obj))
+           == NULL) {
+               syslog(LOG_NOTICE, "script_reload() - context_create()");
+               goto err;
+       }
+
+       if ((script = JS_CompileFile(ctx, obj, CONF.SCRIPT_PATH)) == NULL) {
+               syslog(LOG_NOTICE, "script_reload() - JS_CompileFile()");
+               goto err;
+       }
+       if (!JS_ExecuteScript(ctx, obj, script, &val)) {
+               syslog(LOG_NOTICE, "script_reload() - JS_ExecuteScript()");
+               goto err;
+       }
+
+       /*
+        * Create a rooted object which can be used to store arguments objects
+        * sent to called functions
+        */
+       if ((robj = JS_NewObject(ctx, NULL, NULL, NULL)) == NULL) {
+               syslog(LOG_NOTICE, "script_reload() - JS_NewObject()");
+               goto err;
+       }
+       p_robj = OBJECT_TO_JSVAL(robj);
+       if (!JS_AddRoot(ctx, &p_robj))
+               syslog(LOG_NOTICE, "script_reload() - JS_AddRoot()");
+
+       /* Trick to free previous values :) */
+       (void) script_reload(NULL);
+       p_obj = obj;
+       p_script = script;
+       p_ctx = ctx;
+       p_rt = rt;
+
+       return 0;
+
+err:
+       if (script && ctx)
+               JS_DestroyScript(ctx, script);
+       if (ctx)
+               JS_DestroyContext(ctx);
+       if (rt)
+               JS_DestroyRuntime(rt);
+
+       syslog(LOG_NOTICE,
+           "script_reload() - Preserving previous script if any");
+
+       return -1;
+}
+
+static int
+parent_init_hook(void)
+{
+
+       return script_reload(CONF.SCRIPT_PATH);
+}
+
+static void
+parent_exit_hook(void)
+{
+
+       (void) script_reload(NULL);
+}
+
+static void
+parent_sighup_hook(void)
+{
+
+       syslog(LOG_NOTICE, "Received SIGHUP, reloading JS application");
+
+       if (script_reload(CONF.SCRIPT_PATH) == 0)
+               server_recycle(CONF.SIGHUP_INTERRUPT_CHILDREN);
+}
+
+static int
+child_init_hook(void)
+{
+       jsval   args, ret;
+
+       if (CONF.RLIMITS && rlimit_security() == -1)
+               return -1;
+
+       if (!JS_CallFunctionName(p_ctx, p_obj, "child_init_hook", 0, &args,
+           &ret)) {
+               syslog(LOG_NOTICE,
+                   "child_init_hook() - JS_CallFunctionName()");
+               return -1;
+       }
+
+       return 0;
+}
+
+static void
+child_exit_hook(void)
+{
+       jsval   args, ret;
+
+       if (!JS_CallFunctionName(p_ctx, p_obj, "child_exit_hook", 0, &args,
+           &ret))
+               syslog(LOG_NOTICE,
+                   "child_exit_hook() - JS_CallFunctionName()");
+}
+
+static void
+child_sigalrm_hook(void)
+{
+       jsval   args, ret;
+
+       if (!JS_CallFunctionName(p_ctx, p_obj, "child_sigalrm_hook", 0, &args,
+           &ret))
+               syslog(LOG_NOTICE,
+                   "child_sigalrm_hook() - JS_CallFunctionName()");
+}
+
+static JSObject        *
+server_request_object(struct server_request *r)
+{
+       JSObject        *o;
+
+       if ((o = JS_NewObject(p_ctx, NULL, NULL, NULL)) == NULL) {
+               syslog(LOG_NOTICE, "request_handler() - JS_NewObject()");
+               goto err;
+       }
+       /* Root object immediately */
+       if (!JS_DefineProperty(p_ctx, JSVAL_TO_OBJECT(p_robj), "args",
+           OBJECT_TO_JSVAL(o), NULL, NULL, JSPROP_ENUMERATE)) {
+               syslog(LOG_NOTICE, "request_handler() - Root");
+               goto err;
+       }
+
+       if (!JS_DefineProperty(p_ctx, o, "server_socket",
+           INT_TO_JSVAL(r->server_socket), NULL, NULL, JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "client_socket",
+           INT_TO_JSVAL(r->client_socket), NULL, NULL, JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "server_socket_type",
+           INT_TO_JSVAL(r->server_socket_type), NULL, NULL,
+           JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "server_socket_family",
+           INT_TO_JSVAL(r->server_socket_family), NULL, NULL,
+           JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "client_port",
+           INT_TO_JSVAL(r->client_port), NULL, NULL, JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "server_socket_port",
+           INT_TO_JSVAL(r->server_socket_port), NULL, NULL,
+           JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "server_socket_address_name",
+           STRING_TO_JSVAL(JS_NewStringCopyZ(p_ctx,
+           r->server_socket_address_name)), NULL, NULL, JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "client_address_name",
+           STRING_TO_JSVAL(JS_NewStringCopyZ(p_ctx, r->client_address_name)),
+           NULL, NULL, JSPROP_ENUMERATE))
+               goto err2;
+       if (!JS_DefineProperty(p_ctx, o, "client_address_hostname",
+           STRING_TO_JSVAL(JS_NewStringCopyZ(p_ctx,
+           r->client_address_hostname)), NULL, NULL, JSPROP_ENUMERATE))
+               goto err2;
+       {
+               jsval   val;
+
+               /* uint32_t so might require a double JS object */
+               if (!JS_NewDoubleValue(p_ctx,
+                   (jsdouble)r->client_address_concurrency, &val)) {
+                       syslog(LOG_NOTICE,
+                           "server_request_object() - JS_NewDoubleValue()");
+                       goto err;
+               }
+               if (!JS_DefineProperty(p_ctx, o, "client_address_concurrency",
+                       val, NULL, NULL, JSPROP_ENUMERATE))
+                       goto err2;
+       }
+       if (r->packet_data != NULL) {
+               if (!JS_DefineProperty(p_ctx, o, "packet_data",
+                   STRING_TO_JSVAL(JS_NewStringCopyN(p_ctx, r->packet_data,
+                   r->packet_size)), NULL, NULL, JSPROP_ENUMERATE))
+               goto err2;
+       }
+
+       return o;
+
+err2:
+       syslog(LOG_NOTICE, "server_request_object() - JS_DefineProperty()");
+err:
+       return NULL;
+}
+
+static void
+server_request_object_destroy(void)
+{
+
+       (void) JS_DeleteProperty(p_ctx, JSVAL_TO_OBJECT(p_robj), "args");
+}
+
+static void
+request_handler(struct server_request *r)
+{
+       JSObject        *o;
+       jsval           args[1], ret;
+
+       syslog(LOG_NOTICE, "Request from [%s]", r->client_address_name);
+
+       if ((o = server_request_object(r)) == NULL) {
+               syslog(LOG_NOTICE,
+                   "request_handler() - server_request_object()");
+               goto err;
+       }
+       *args = OBJECT_TO_JSVAL(o);
+
+       if (!JS_CallFunctionName(p_ctx, p_obj, "request_handler", 1, args,
+           &ret))
+               syslog(LOG_NOTICE,
+                   "request_handler() - JS_CallFunctionName()");
+
+err:
+       if (o != NULL)
+               server_request_object_destroy();
+}
+
+static void
+reject_handler(struct server_request *r, int res)
+{
+       JSObject        *o;
+       jsval           args[2], ret;
+
+       syslog(LOG_NOTICE, "Rejected request from [%s]",
+           r->client_address_name);
+
+       if ((o = server_request_object(r)) == NULL) {
+               syslog(LOG_NOTICE,
+                   "reject_handler() - server_request_object()");
+               goto err;
+       }
+       args[0] = OBJECT_TO_JSVAL(o);
+       args[1] = INT_TO_JSVAL(res);
+
+       if (!JS_CallFunctionName(p_ctx, p_obj, "reject_handler", 2, args,
+           &ret))
+               syslog(LOG_NOTICE, "reject_handler() - JS_CallFunctionName()");
+
+err:
+       if (o != NULL)
+               server_request_object_destroy();
+}
+
+static void
+request_close_hook(struct server_request *r)
+{
+       JSObject        *o;
+       jsval           args[1], ret;
+
+       syslog(LOG_NOTICE, "Closing for [%s]", r->client_address_name);
+
+       if ((o = server_request_object(r)) == NULL) {
+               syslog(LOG_NOTICE,
+                   "request_close_hook() - server_request_object()");
+               goto err;
+       }
+       *args = OBJECT_TO_JSVAL(o);
+
+       if (!JS_CallFunctionName(p_ctx, p_obj, "request_close_hook", 1, args,
+           &ret))
+               syslog(LOG_NOTICE,
+                   "request_close_hook() - JS_CallFunctionName()");
+
+err:
+       if (o != NULL)
+               server_request_object_destroy();
+}
+
+static void
+request_interrupt_hook(struct server_request *r)
+{
+       JSObject        *o;
+       jsval           args[1], ret;
+
+       syslog(LOG_NOTICE, "Interrupted by SIGHUP");
+
+       if ((o = server_request_object(r)) == NULL) {
+               syslog(LOG_NOTICE,
+                   "request_interrupt_hook() - server_request_object()");
+               goto err;
+       }
+       *args = OBJECT_TO_JSVAL(o);
+
+       if (!JS_CallFunctionName(p_ctx, p_obj, "request_interrupt_hook", 1,
+           args, &ret))
+               syslog(LOG_NOTICE,
+                   "request_interrupt_hook() - JS_CallFunctionName()");
+
+err:
+       if (o != NULL)
+               server_request_object_destroy();
+}
diff --git a/mmsoftware/js/js-appserv/src/js-appserv.conf.5 b/mmsoftware/js/js-appserv/src/js-appserv.conf.5
new file mode 100644 (file)
index 0000000..9049c74
--- /dev/null
@@ -0,0 +1,279 @@
+.\" $Id: js-appserv.conf.5,v 1.1 2006/08/20 06:59:00 mmondor Exp $
+.\"
+.\" Copyright (C) 2006, Matthew Mondor
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"      This product includes software written by Matthew Mondor.
+.\" 4. The name of Matthew Mondor may not be used to endorse or promote
+.\"    products derived from this software without specific prior written
+.\"    permission.
+.\" 5. Redistribution of source code may not be released under the terms of
+.\"    any GNU Public License derivate.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
+.\" IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd June 24, 2006
+.Dt JS-CGID.CONF 5
+.Os mmsoftware
+.Sh NAME
+.Nm js-appserv.conf
+.Nd
+.Xr js-appserv.conf 5
+configuration file for
+.Xr js-appserv 8
+.Sh DESCRIPTION
+The
+.Nm /usr/local/etc/js-appserv.conf
+file may contain one or more keyword/value pairs per line, empty lines or
+comments.  A ';' or '#' character causes all other characters to be considered
+as a comment, and to be ignored, until the end of the current line. It is
+very important to enclose the value for a keyword in double quotes ('"'
+characters) if the following characters are found in it: ';', '#', space and
+tab. It is allowed to use an equal sign '=' between the keyword name and it's
+argument. Here are documented the various possible keywords and their
+allowed values, as well as their defaults.
+.Pp
+.Ss Service administration parameters
+.Pp
+.Bl -tag -width indent -offset indent
+.It Nm CHROOT_DIR Ar "directory"
+If specified and non-empty, causes the daemon to use
+.Xr chroot 2
+at initialization so that it becomes jailed into the wanted alternative root
+directory. Obviously, all required files for the application should have a copy
+within the new root (this may include files such as
+.Nm /etc/resolv.conf ,
+.Nm /etc/hosts ,
+.Nm /etc/passwd ,
+.Nm /etc/group ,
+a few shared libraries, the executable binary to be launched, and so on,
+as required by the application and libc.
+.It Nm LOCK_PATH Ar "fullpath"
+Specifies the location where the internal synchronization (and anti-recursive
+runlock) are to be created (before chrooting). Should consist of an absolute
+full pathname including a filename, after which will automatically be postfixed
+extensions for the various locks.
+When several server instances are run concurrently to serve multiple services,
+the name of the lock files should be different for each to not conflict.
+.It Nm PID_PATH Ar "fullpath"
+Tells where to store the file holding the server process ID to be killed with
+.Dv SIGTERM
+(sig 15) to cause the server to cleanly exit. This is done before chrooting.
+When several server instances are run concurrently to serve multiple services,
+the name of the PID files should be different for each to not conflict.
+.It Nm USER Ar "user"
+At server initialization, it drops privileges from the superuser to the
+specified user, definitively. This can be specified as either a username
+or it's user ID number.
+.It Nm GROUPS Ar "group,..."
+When dropping privileges, the process will become part of these groups.
+More than one group may be specified, by name or ID, separated by commas,
+without spaces. The first group will be set to the real process group, and
+others as secondary access ones.
+.It Nm LOG_FACILITY Ar "facility"
+Syslog facility which should be used for error logging. Should normally be
+one of
+.Dv LOG_AUTH , LOG_AUTHPRIV , LOG_CRON,  LOG_DAEMON ,
+.Dv LOG_FTP , LOG_KERN , LOG_LPR , LOG_MAIL ,
+.Dv LOG_NEWS , LOG_SYSLOG , LOG_USER
+or
+.Dv LOG_UUCP .
+See
+.Xr syslog 3
+man page for more information.
+.It Nm PROCTITLE Ar "string"
+This is useful if multiple services are served using
+.Xr js-appserv 8
+for commands such as
+.Xr ps 1 ,
+notably on BSD systems. Where available, this causes the supplied string
+to prefix the comments attached to the processes using
+.Xr setproctitle 3 .
+It also is appended to the daemon name for
+.Xr syslog 3
+at
+.Xr openlog 3
+time.
+.El
+.Ss TCP server administration
+.Bl -tag -width indent -offset indent
+.It Nm LISTEN_ADDRESSES Ar "address ..."
+Tells to which interfaces the server should listen to, separated by spaces.
+The arguments should be enclosed in double quotes if more than one address
+is supplied. These addresses are used for
+.Xr bind 2 .
+Specifying "0.0.0.0" causes
+.Nm js-appserv
+to listen to all interfaces.
+.It Nm LISTEN_PORT Ar "number"
+Supplies which TCP port number to listen to. This must be a numeric port number
+within the range of 1 to 65535.
+.It Nm MAX_CONNECTIONS Ar "number"
+The maximum number of simultaneous clients which should be served at once.
+.It Nm MAX_ADDRESSES Ar "number"
+The maximum number of simultaneous different client IP addresses to serve.
+.It Nm MAX_PER_ADDRESS Ar "number"
+The maximum number of simultaneous clients to serve at once per IP address.
+.It Nm CONNECTION_RATE Ar "number"
+The maximum number of connections to accept from each address within
+.Nm CONNECTION_PERIOD .
+Can be 0 to disable connection rate throttling.
+.It Nm CONNECTION_PERIOD Ar "number"
+If
+.Nm CONNECTION_RATE
+is non-zero, specifies the number of seconds during which a maximum of
+.Nm CONNECTION_RATE
+connections are to be allowed.
+.It Nm RESOLVE_ADDRESSES Ar "boolean"
+Specifies weither client addresses should be resolved to hostnames when
+logging the connection event via
+.Xr syslog 3 .
+An internal cache is maintained for recently connected addresses for faster
+performance. This is done in the child serving process so that it does not
+slow down the listener process responsible for accepting new connections.
+Should be TRUE or FALSE.
+.El
+.Ss Application security limits
+.Bl -tag -width indent -offset indent
+.It Nm RLIMITS Ar "boolean"
+If TRUE, all following
+.Nm RLIMIT_*
+parameters will be applied to the children processes before launching the
+application command if they are not set to -1 values.
+.Xr setrlimit 2
+is called to perform this. Note that these should be set to sane values
+for proper function, by a competent administrator, if this option is
+enabled. When disabled, or if enabled but for each following option specified
+with -1, the defaults are used, which are inherited from the server process.
+.Bl -tag -width indent -offset indent
+.It Nm RLIMIT_CORE Ar "value"
+If not -1, the largest size (in bytes) core file that may be created.
+.It Nm RLIMIT_CPU Ar "value"
+If not -1, The maximum amount of cpu time (in seconds) to be used by
+each process.
+.It Nm RLIMIT_DATA Ar "value"
+-1 or The maximum size (in bytes) of the data segment for a process;
+this defines how far a program may extend its break with the
+.Xr sbrk 2
+or
+.Xr mmap 2
+system calls.
+.It Nm RLIMIT_FSIZE Ar "value"
+The largest size (in bytes) file that may be created, or -1.
+.It Nm RLIMIT_MEMLOCK Ar "value"
+The maximum size (in bytes) which a process may lock into physical memory
+(wire) using the
+.Xr mlock 2
+system call function, or -1.
+.It Nm RLIMIT_NOFILE Ar "value"
+If not -1, the maximum number of open files for this process.
+.It Nm RLIMIT_NPROC Ar "value"
+The maximum number of simultaneous processes for this user ID, or -1.
+.It Nm RLIMIT_RSS Ar "value"
+The maximum size (in bytes) to which a process's resident
+set size may grow.  This imposes a limit on the amount of
+physical memory to be given to a process; if memory is
+tight, the system will prefer to take memory from processes
+that are exceeding their declared resident set size.
+-1 to use the defaults.
+.It Nm RLIMIT_STACK Ar "value"
+If not -1, the maximum size (in bytes) of the stack segment for a
+process; this defines how far a program's stack segment
+may be extended.  Stack extension is performed automatically by the system.
+.El
+.El
+.Ss Debugging support
+.Bl -tag -width indent -offset indent
+.It Nm STDERR_FILE Ar "fullpath"
+XXX
+By default, the standard error stream (stderr) of
+.Nm COMMAND
+is redirected to the "/dev/null" device. However, it may be nice to be able
+to obtain those messages from time to time, or to see if any are generated
+by the application. This option, if non-empty, creates, or appends to the
+specified file (outside of the chroot setup). This option should not be
+used on production systems, as the file will grow without bounds. If a log
+rotation system is used,
+.Xr js-appserv 8
+will require to be restarted everytime it is ran. It is merely for debugging.
+.El
+.Sh DEFAULTS
+The following defaults are used:
+.Pp
+.Bd -literal -offset indent
+CHROOT_DIR             ""
+LOCK_PATH              "/var/run/js-appserv.lock"
+PID_PATH               "/var/run/js-appserv.pid"
+USER                   "js-appserv"
+GROUPS                 "js-appserv"
+LOG_FACILITY           "LOG_AUTHPRIV"
+PROCTITLE              ""
+
+LISTEN_ADDRESSES       "127.0.0.1"
+LISTEN_PORT            2323
+MAX_CONNECTIONS                32
+MAX_ADDRESSES          32
+MAX_PER_ADDRESS                1
+CONNECTION_RATE                5
+CONNECTION_PERIOD      30
+RESOLVE_ADDRESSES      FALSE
+
+RLIMITS                        FALSE
+RLIMIT_CORE            -1
+RLIMIT_CPU             -1
+RLIMIT_DATA            -1
+RLIMIT_FSIZE           -1
+RLIMIT_MEMLOCK         -1
+RLIMIT_NOFILE          -1
+RLIMIT_NPROC           -1
+RLIMIT_RSS             -1
+RLIMIT_STACK           -1
+
+STDERR_FILE            ""
+.Ed
+.Sh AUTHOR
+.Nm js-appserv
+was written by Matthew Mondor, and is
+Copyright (c) 2006, Matthew Mondor, All rights reserved.
+.Sh FILES
+.Bl -tag -width indent -offset indent
+.It Pa /usr/local/etc/js-appserv.conf
+This file
+.It Pa /usr/local/sbin/js-appserv
+The
+.Xr js-appserv 8
+server binary itself.
+.El
+.Sh SEE ALSO
+.Xr js-appserv 8 ,
+.Xr syslog 3 ,
+.Xr openlog 3 ,
+.Xr chroot 2 ,
+.Xr bind 2 ,
+.Xr setrlimit 2 ,
+.Xr ps 1 ,
+.Xr setproctitle 3 .
+.Sh BUGS
+Not really a bug, but tied to each interface could be most connection control
+options.
+.Pp
+Please report any bug to mmondor@accela.net