diff --git a/WHATSNEW.md b/WHATSNEW.md index ec186f08..dcf4af4d 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -9,6 +9,7 @@ This document attempts to track **major** changes and additions in ENiGMA½. For * New `PV` ACS check for arbitrary user properties. See [ACS](./docs/configuration/acs.md) for details. * The `message` arg used by `msg_list` has been deprecated. Please starting using `messageIndex` for this purpose. Support for `message` will be removed in the future. * Added ability to export/download messages. This is enabled in the default menu. See `messageAreaViewPost` in [the default message base template](./misc/menu_templates/message_base.in.hjson) and look for the download options (`@method:addToDownloadQueue`, etc.) for details on adding to your system! +* The Gopher server has had a revamp! Standard `gophermap` files are now served along with any other content you configure for your Gopher Hole! A default [gophermap](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) can be found [in the misc directory](./misc/gophermap) that behaves like the previous implementation. See [Gopher docs](./docs/servers/gopher.md) for more information. ## 0.0.11-beta * Upgraded from `alpha` to `beta` -- The software is far along and mature enough at this point! diff --git a/core/config_default.js b/core/config_default.js index 82d3eb1d..98aeb4af 100644 --- a/core/config_default.js +++ b/core/config_default.js @@ -263,7 +263,7 @@ module.exports = () => { port : 8070, publicHostname : 'another-fine-enigma-bbs.org', publicPort : 8070, // adjust if behind NAT/etc. - bannerFile : 'gopher_banner.asc', + staticRoot : paths.join(__dirname, './../gopher'), // // Set messageConferences{} to maps of confTag -> [ areaTag1, areaTag2, ... ] diff --git a/core/servers/content/gopher.js b/core/servers/content/gopher.js index 4e51c889..442ecdcb 100644 --- a/core/servers/content/gopher.js +++ b/core/servers/content/gopher.js @@ -27,6 +27,7 @@ const _ = require('lodash'); const fs = require('graceful-fs'); const paths = require('path'); const moment = require('moment'); +const async = require('async'); const ModuleInfo = exports.moduleInfo = { name : 'Gopher', @@ -81,8 +82,8 @@ exports.getModule = class GopherModule extends ServerModule { this.publicHostname = config.contentServers.gopher.publicHostname; this.publicPort = config.contentServers.gopher.publicPort; - this.addRoute(/^\/?\r\n$/, this.defaultGenerator); - this.addRoute(/^\/msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator); + this.addRoute(/^\/?msgarea(\/[a-z0-9_-]+(\/[a-z0-9_-]+)?(\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(_raw)?)?)?\/?\r\n$/, this.messageAreaGenerator); + this.addRoute(/^(\/?[^\t\r\n]*)\r\n$/, this.staticGenerator); this.server = net.createServer( socket => { socket.setEncoding('ascii'); @@ -161,22 +162,56 @@ exports.getModule = class GopherModule extends ServerModule { return `${itemType}${text}\t${selector}\t${hostname}\t${port}\r\n`; } - defaultGenerator(selectorMatch, cb) { - this.log.debug( { selector : selectorMatch[0] }, 'Serving default content'); + staticGenerator(selectorMatch, cb) { + this.log.debug( { selector : selectorMatch[1] || '(gophermap)' }, 'Serving static content'); - let bannerFile = _.get(Config(), 'contentServers.gopher.bannerFile', 'gopher_banner.asc'); - bannerFile = paths.isAbsolute(bannerFile) ? bannerFile : paths.join(__dirname, '../../../misc', bannerFile); - fs.readFile(bannerFile, 'utf8', (err, banner) => { - if(err) { - return cb('You have reached an ENiGMA½ Gopher server!'); + const requestedPath = selectorMatch[1]; + let path = this.resolveContentPath(requestedPath); + if (!path) { + return cb('Not found'); + } + + fs.stat(path, (err, stats) => { + if (err) { + return cb('Not found'); } - banner = splitTextAtTerms(banner).map(l => this.makeItem(ItemTypes.InfoMessage, l)).join(''); - banner += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea'); - return cb(banner); + let isGopherMap = false; + if (stats.isDirectory()) { + path = paths.join(path, 'gophermap'); + isGopherMap = true; + } + + fs.readFile(path, isGopherMap ? 'utf8' : null, (err, content) => { + if (err) { + let content = 'You have reached an ENiGMA½ Gopher server!\r\n'; + content += this.makeItem(ItemTypes.SubMenu, 'Public Message Area', '/msgarea'); + return cb(content); + } + + if (isGopherMap) { + // Convert any UNIX style LF's to DOS CRLF's + content = content.replace(/\r?\n/g, '\r\n'); + + // variable support + content = content + .replace(/{publicHostname}/g, this.publicHostname) + .replace(/{publicPort}/g, this.publicPort); + } + + return cb(content); + }); }); } + resolveContentPath(requestPath) { + const staticRoot = _.get(Config(), 'contentServers.gopher.staticRoot'); + const path = paths.resolve(staticRoot, `.${requestPath}`); + if (path.startsWith(staticRoot)) { + return path; + } + } + notFoundGenerator(selector, cb) { this.log.debug( { selector }, 'Serving not found content'); return cb('Not found'); diff --git a/docs/servers/gopher.md b/docs/servers/gopher.md index 343c7d80..2877fd80 100644 --- a/docs/servers/gopher.md +++ b/docs/servers/gopher.md @@ -3,7 +3,7 @@ layout: page title: Gopher Server --- ## The Gopher Content Server -The Gopher *content server* provides access to publicly exposed message conferences and areas over Gopher (gopher://). +The Gopher *content server* provides access to publicly exposed message conferences and areas over Gopher (gopher://) as well as any other content you wish to serve in your Gopher Hole! ## Configuration Gopher configuration is found in `contentServers.gopher` in `config.hjson`. @@ -11,14 +11,36 @@ Gopher configuration is found in `contentServers.gopher` in `config.hjson`. | Item | Required | Description | |------|----------|-------------| | `enabled` | :+1: | Set to `true` to enable Gopher | +| `staticRoot` | :+1: | Sets the path serving as the static root path for all Gopher content. Defaults to `enigma-bbs/gopher`.
See also **Gophermap's** below | | `port` | :-1: | Override the default port of `8070` | -| `publicHostName` | :+1: | Set the **public** hostname/domain that Gopher will serve to the outside world. Example: `myfancybbs.com` | +| `publicHostname` | :+1: | Set the **public** hostname/domain that Gopher will serve to the outside world. Example: `myfancybbs.com` | | `publicPort` | :+1: | Set the **public** port that Gopher will serve to the outside world. | -| `messageConferences` | :+1: | An map of *conference tags* to *area tags* that are publicly exposed via Gopher. See example below. | +| `messageConferences` | :-1: | An map of *conference tags* to *area tags* that are publicly exposed via Gopher. See example below. | -Notes on `publicHostName` and `publicPort`: +Notes on `publicHostname` and `publicPort`: The Gopher protocol serves content that contains host/domain and port even when referencing it's own documents. Due to this, these members must be set to your publicly addressable Gopher server! +## Gophermap's +[Gophermap's](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) are how to build menus for your Gopher Hole. Each map is a simple text file named `gophermap` (all lowercase, no extension) with DOS style CRLF endings. + +Within any directory nested within your `staticRoot` may live a `gophermap`. A template may be found in the `enigma-bbsmisc` directory. + +ENiGMA will pre-process `gophermap` files replacing in following variables: +* `{publicHostname}`: The public hostname from your config. +* `{publicPort}`: The public port from your config. + +:information_source: See [Wikipedia](https://en.wikipedia.org/wiki/Gopher_(protocol)#Source_code_of_a_menu) for more information on the `gophermap` format. + +:bulb: Tools such as [gfu](https://rawtext.club/~sloum/gfu.html) may help you with `gophermap`'s + +### Example Gophermap +An example `gophermap` living in `enigma-bbs/gopher`: +``` +iWelcome to a Gopher server! {publicHostname} {publicPort} +1Public Message Area /msgarea {publicHostname} {publicPort} +. +``` + ### Example Let's suppose you are serving Gopher for your BBS at `myfancybbs.com`. Your ENiGMA½ system is listening on the default Gopher `port` of 8070 but you're behind a firewall and want port 70 exposed to the public. Lastly, you want to expose some fsxNet areas: @@ -26,9 +48,10 @@ Let's suppose you are serving Gopher for your BBS at `myfancybbs.com`. Your ENiG contentServers: { gopher: { enabled: true - publicHostName: myfancybbs.com + publicHostname: myfancybbs.com publicPort: 70 + // Expose some public message conferences/areas messageConferences: { fsxnet: { // fsxNet's conf tag // Areas of fsxNet we want to expose: diff --git a/misc/config_template.in.hjson b/misc/config_template.in.hjson index 1db5406d..21cefeb6 100644 --- a/misc/config_template.in.hjson +++ b/misc/config_template.in.hjson @@ -242,8 +242,8 @@ port: XXXXX enabled: false - // bannerFile path in misc/ by default. Full paths allowed. - bannerFile: XXXXX + // The root directory to serve gophermaps and other content + staticRoot: XXXXX // // The Gopher Content Server can export message base diff --git a/misc/gopher_banner.asc b/misc/gopher_banner.asc deleted file mode 100644 index b758e066..00000000 --- a/misc/gopher_banner.asc +++ /dev/null @@ -1,9 +0,0 @@ -_____________________ _____ ____________________ __________\_ / -\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp! -// __|___// | \// |// | \// | | \// \ /___ /_____ -/____ _____| __________ ___|__| ____| \ / _____ \ ----- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/ - /__ _\ - <*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/ - -------------------------------------------------------------------------------- diff --git a/misc/gophermap b/misc/gophermap new file mode 100644 index 00000000..c4973670 --- /dev/null +++ b/misc/gophermap @@ -0,0 +1,12 @@ +i_____________________ _____ ____________________ __________\_ / +i\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp! +i// __|___// | \// |// | \// | | \// \ /___ /_____ +i/____ _____| __________ ___|__| ____| \ / _____ \ +i---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/ +i /__ _\ +i <*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/ +i +i------------------------------------------------------------------------------- +i +1Public Message Area /msgarea {publicHostname} {publicPort} +. diff --git a/misc/install.sh b/misc/install.sh index 741c4585..9a894a37 100755 --- a/misc/install.sh +++ b/misc/install.sh @@ -146,6 +146,12 @@ install_node_packages() { fi } +copy_template_files() { + if [[ ! -f "./gopher/gophermap" ]]; then + cp "./misc/gophermap" "./gopher/gophermap" + fi +} + enigma_footer() { log "ENiGMA½ installation complete!" echo -e "\e[1;33m" @@ -189,6 +195,7 @@ install_nvm configure_nvm download_enigma_source install_node_packages +copy_template_files enigma_footer } # this ensures the entire script is downloaded before execution