diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..fcb42c10 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,9 @@ +FROM library/node:lts-bookworm + +ARG DEBIAN_FRONTEND=noninteractive +RUN apt update \ + && apt install -y --no-install-recommends sudo telnet \ + && apt autoremove -y \ + && rm -rf /var/lib/apt/lists/* \ + && echo "node ALL=(ALL) NOPASSWD: ALL" >/etc/sudoers.d/node \ + && chmod 0440 /etc/sudoers.d/node diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..7c1a9245 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Basic Node.js", + "build": { "dockerfile": "Dockerfile" }, +"remoteUser": "root", +"forwardPorts": [8888, 4000], +"postCreateCommand": "gem install jekyll bundler && /bin/rm -rf node_modules && npm install && cd docs && bundle install && cd ..", +"features": { + "ghcr.io/devcontainers/features/python:1": { + "installTools": true, + "version": "3.11" + }, + "ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {}, + "ghcr.io/jungaretti/features/ripgrep:1": {}, + "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}, + "ghcr.io/devcontainers/features/ruby:1": { + "version": "3.1" + } +}, +"customizations": { + "vscode": { + "extensions": ["ms-azuretools.vscode-docker","alexcvzz.vscode-sqlite","yzhang.markdown-all-in-one", "DavidAnson.vscode-markdownlint", "christian-kohler.npm-intellisense", "dbaeumer.vscode-eslint", "bierner.markdown-yaml-preamble"] + } +} +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..749db005 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +package-lock.json +yarn.lock + +filebase +db +drop +file_base +logs +mail +docs/_site +docs/.sass-cache \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index b1892ede..5463d1aa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,13 @@ *.TXT eol=crlf *.diz eol=crlf *.DIZ eol=crlf + +# Don't mess with shell script line endings +*.sh text eol=lf + +# Same thing for optutil.js which functions as a shell script +optutil.js text eol=lf + +# The devcontainer is also unix +.devcontainer/Dockerfile text eol=lf +.devcontainer/devcontainer.json text eol=lf \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 43e0e6cf..165fc522 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -11,23 +11,23 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: tags: enigmabbs/enigma-bbs:latest file: docker/Dockerfile diff --git a/.gitignore b/.gitignore index b55cbcfa..7479afc1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ mail/ node_modules/ docs/_site/ docs/.sass-cache/ -.vscode/ + +docs/.jekyll-cache/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..bfd63065 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-vscode-remote.remote-containers", + "laktak.hjson" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..8f033263 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,26 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Jekyll (ENiGMA½ documentation server)", + "command": "cd docs && bundle exec jekyll serve", + "isBackground": true, + "type": "shell" + }, + { + "label": "(re)build Jekyll bundles", + "command": "cd docs && bundle install", + "type": "shell" + }, + { + "label": "(re)build node modules", + "command": "/bin/rm -rf node_modules && npm install", + "type": "shell" + }, + { + "label": "ENiGMA½ new configuration", + "command": "./oputil.js config new", + "type": "shell" + } + ] +} \ No newline at end of file diff --git a/UPGRADE.md b/UPGRADE.md index 5f2458be..3a4f8c2c 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -35,6 +35,10 @@ npm install # or simply 'yarn' ## 0.0.13-beta to 0.0.14-beta +* Due to changes to supported algorithms in newer versions of openssl, the default list of supported algorithms for the ssh login server has changed. There are both removed ciphers as well as optional new kex algorithms available now. ***NOTE:*** Changes to supported algorithms are only needed to support keys generated with new versions of openssl, if you already have a ssl key in use you should not have to make any changes to your config. + * Removed ciphers: 'blowfish-cbc', 'arcfour256', 'arcfour128', and 'cast128-cbc' + * Added kex: 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521' + ## 0.0.12-beta to 0.0.13-beta * To enable the new Waiting for Caller (WFC) support, please see [WFC](docs/modding/wfc.md). * :exclamation: The SSH server's `ssh2` module has gone through a major upgrade. Existing users will need to comment out two SSH KEX algorithms from their `config.hjson` if present else clients such as NetRunner will not be able to connect over SSH. Comment out `diffie-hellman-group-exchange-sha256` and `diffie-hellman-group-exchange-sha1` diff --git a/WHATSNEW.md b/WHATSNEW.md index 060bf502..3216f83d 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -9,6 +9,8 @@ This document attempts to track **major** changes and additions in ENiGMA½. For * Finally, the system will search for `index.html` and `index.htm` in that order, if another suitable route cannot be established. * CombatNet has shut down, so the module (`combatnet.js`) has been removed. * The Menu Flag `popParent` has been removed and `noHistory` has been updated to work as expected. In general things should "Just Work", but check your `menu.hjson` entries if you see menu stack issues. +* Various New User Application (NUA) properties are now optional. If you would like to reduce the information users are required, remove optional fields from NUA artwork and collect less. These properties will be stored as "" (empty). Optional properties are as follows: Real name, Birth date, Sex, Location, Affiliations (Affils), Email, and Web address. +* Art handling has been changed to respect the art width contained in SAUCE when present in the case where the terminal width is greater than the art width. This fixes art files that assume wrapping at 80 columns on wide (mostly new utf8) terminals. ## 0.0.13-beta * **Note for contributors**: ENiGMA has switched to [Prettier](https://prettier.io) for formatting/style. Please see [CONTRIBUTING](CONTRIBUTING.md) and the Prettier website for more information. diff --git a/art/themes/luciano_blocktronics/2FACONFSCR.ans b/art/themes/luciano_blocktronics/2FACONFSCR.ans index 3c256000..114c25ab 100644 Binary files a/art/themes/luciano_blocktronics/2FACONFSCR.ans and b/art/themes/luciano_blocktronics/2FACONFSCR.ans differ diff --git a/art/themes/luciano_blocktronics/BBSADD.ANS b/art/themes/luciano_blocktronics/BBSADD.ANS index f341f486..1a34d884 100644 Binary files a/art/themes/luciano_blocktronics/BBSADD.ANS and b/art/themes/luciano_blocktronics/BBSADD.ANS differ diff --git a/art/themes/luciano_blocktronics/BBSLIST.ANS b/art/themes/luciano_blocktronics/BBSLIST.ANS index ec2974e9..c4abd674 100644 Binary files a/art/themes/luciano_blocktronics/BBSLIST.ANS and b/art/themes/luciano_blocktronics/BBSLIST.ANS differ diff --git a/art/themes/luciano_blocktronics/FBLISTEXP.ANS b/art/themes/luciano_blocktronics/FBLISTEXP.ANS index 73a567f0..8ab58f05 100644 Binary files a/art/themes/luciano_blocktronics/FBLISTEXP.ANS and b/art/themes/luciano_blocktronics/FBLISTEXP.ANS differ diff --git a/art/themes/luciano_blocktronics/FDLMGR.ANS b/art/themes/luciano_blocktronics/FDLMGR.ANS index a85ffcbe..a5c1cedf 100644 Binary files a/art/themes/luciano_blocktronics/FDLMGR.ANS and b/art/themes/luciano_blocktronics/FDLMGR.ANS differ diff --git a/art/themes/luciano_blocktronics/FWDLMGR.ANS b/art/themes/luciano_blocktronics/FWDLMGR.ANS index b0e7fffc..a5012263 100644 Binary files a/art/themes/luciano_blocktronics/FWDLMGR.ANS and b/art/themes/luciano_blocktronics/FWDLMGR.ANS differ diff --git a/art/themes/luciano_blocktronics/MSGLIST.ANS b/art/themes/luciano_blocktronics/MSGLIST.ANS index b67b31a6..44d5163d 100644 Binary files a/art/themes/luciano_blocktronics/MSGLIST.ANS and b/art/themes/luciano_blocktronics/MSGLIST.ANS differ diff --git a/art/themes/luciano_blocktronics/MSRCHLST.ANS b/art/themes/luciano_blocktronics/MSRCHLST.ANS index 4a9982ff..73a62a2a 100644 Binary files a/art/themes/luciano_blocktronics/MSRCHLST.ANS and b/art/themes/luciano_blocktronics/MSRCHLST.ANS differ diff --git a/art/themes/luciano_blocktronics/MYMSGLST.ANS b/art/themes/luciano_blocktronics/MYMSGLST.ANS index a56197bc..7c49c1fe 100644 Binary files a/art/themes/luciano_blocktronics/MYMSGLST.ANS and b/art/themes/luciano_blocktronics/MYMSGLST.ANS differ diff --git a/art/themes/luciano_blocktronics/NEWMSGS.ANS b/art/themes/luciano_blocktronics/NEWMSGS.ANS index 90439992..6345db4a 100644 Binary files a/art/themes/luciano_blocktronics/NEWMSGS.ANS and b/art/themes/luciano_blocktronics/NEWMSGS.ANS differ diff --git a/art/themes/luciano_blocktronics/ONELINER.ANS b/art/themes/luciano_blocktronics/ONELINER.ANS index e41069e0..690db12e 100644 Binary files a/art/themes/luciano_blocktronics/ONELINER.ANS and b/art/themes/luciano_blocktronics/ONELINER.ANS differ diff --git a/art/themes/luciano_blocktronics/PRVMSGLIST.ANS b/art/themes/luciano_blocktronics/PRVMSGLIST.ANS index 3969d72f..60a0107b 100644 Binary files a/art/themes/luciano_blocktronics/PRVMSGLIST.ANS and b/art/themes/luciano_blocktronics/PRVMSGLIST.ANS differ diff --git a/art/themes/luciano_blocktronics/STATUS.ANS b/art/themes/luciano_blocktronics/STATUS.ANS index e1618621..7784fdd8 100644 Binary files a/art/themes/luciano_blocktronics/STATUS.ANS and b/art/themes/luciano_blocktronics/STATUS.ANS differ diff --git a/art/themes/luciano_blocktronics/SYSSTAT.ANS b/art/themes/luciano_blocktronics/SYSSTAT.ANS index a76e3bd6..eaad1e7c 100644 Binary files a/art/themes/luciano_blocktronics/SYSSTAT.ANS and b/art/themes/luciano_blocktronics/SYSSTAT.ANS differ diff --git a/art/themes/luciano_blocktronics/ULDETAIL.ANS b/art/themes/luciano_blocktronics/ULDETAIL.ANS index ab3ea881..4454fd17 100644 Binary files a/art/themes/luciano_blocktronics/ULDETAIL.ANS and b/art/themes/luciano_blocktronics/ULDETAIL.ANS differ diff --git a/art/themes/luciano_blocktronics/USERLST.ANS b/art/themes/luciano_blocktronics/USERLST.ANS index 8c67ea58..e1563dc4 100644 Binary files a/art/themes/luciano_blocktronics/USERLST.ANS and b/art/themes/luciano_blocktronics/USERLST.ANS differ diff --git a/art/themes/luciano_blocktronics/WHOSON.ANS b/art/themes/luciano_blocktronics/WHOSON.ANS index 53575482..1dd7f677 100644 Binary files a/art/themes/luciano_blocktronics/WHOSON.ANS and b/art/themes/luciano_blocktronics/WHOSON.ANS differ diff --git a/art/themes/luciano_blocktronics/theme.hjson b/art/themes/luciano_blocktronics/theme.hjson index 582432ec..fde3659c 100644 --- a/art/themes/luciano_blocktronics/theme.hjson +++ b/art/themes/luciano_blocktronics/theme.hjson @@ -187,7 +187,7 @@ } mci: { VM1: { - height: 15, + height: 14 width: 50 itemFormat: "|00|11{userName:<17.17}|03{affils:<21.21}|11{location:<19.19}|03{lastLoginTs}" focusItemFormat: "|00|19|15{userName:<17.17}{affils:<21.21}{location:<19.19}{lastLoginTs}" @@ -343,7 +343,7 @@ } mci: { VM1: { - height: 14 + height: 13 width: 70 itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts:<15.16} |15{newIndicator}" focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts:<15.16} {newIndicator}" @@ -416,7 +416,7 @@ } mci: { VM1: { - height: 14 + height: 12 width: 70 itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts} |15{newIndicator}" focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts} {newIndicator}" @@ -618,7 +618,7 @@ } mci: { VM1: { - height: 16 + height: 12 width: 71 itemFormat: "|00|15 {msgNum:<4.4} |03{subject:<34.33} {fromUserName:<19.18} |03{ts:<12.12}" focusItemFormat: "|00|19> |15{msgNum:<4.4} {subject:<34.33} {fromUserName:<19.18} {ts:<12.12}" @@ -783,7 +783,7 @@ } mci: { VM1: { - height: 14 + height: 12 width: 70 itemFormat: "|00|15 {msgNum:<5.5}|03{subject:<28.27} |15{fromUserName:<20.20} {ts}" focusItemFormat: "|00|19> |15{msgNum:<5.5}{subject:<28.27} {fromUserName:<20.20} {ts}" @@ -1214,7 +1214,7 @@ 2: { mci: { MT1: { - height: 14 + height: 13 width: 45 } diff --git a/art/themes/luciano_blocktronics/wfc.ans b/art/themes/luciano_blocktronics/wfc.ans index 9bf19ca2..823e0d4a 100644 Binary files a/art/themes/luciano_blocktronics/wfc.ans and b/art/themes/luciano_blocktronics/wfc.ans differ diff --git a/art/themes/luciano_blocktronics/wfchelp.ans b/art/themes/luciano_blocktronics/wfchelp.ans index 24ab3bb9..5f261f87 100644 Binary files a/art/themes/luciano_blocktronics/wfchelp.ans and b/art/themes/luciano_blocktronics/wfchelp.ans differ diff --git a/art/themes/luciano_blocktronics/wfckicknodeprompt.ans b/art/themes/luciano_blocktronics/wfckicknodeprompt.ans index a11d90eb..50f1c8eb 100644 Binary files a/art/themes/luciano_blocktronics/wfckicknodeprompt.ans and b/art/themes/luciano_blocktronics/wfckicknodeprompt.ans differ diff --git a/core/achievement.js b/core/achievement.js index d73372ca..582468a3 100644 --- a/core/achievement.js +++ b/core/achievement.js @@ -505,9 +505,9 @@ class Achievements { getFormatObject(info) { return { userName: info.user.username, - userRealName: info.user.properties[UserProps.RealName], - userLocation: info.user.properties[UserProps.Location], - userAffils: info.user.properties[UserProps.Affiliations], + userRealName: info.user.realName(false) || 'N/A', + userLocation: info.user.properties[UserProps.Location] || 'N/A', + userAffils: info.user.properties[UserProps.Affiliations] || 'N/A', nodeId: info.client.node, title: info.details.title, //text : info.global ? info.details.globalText : info.details.text, diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 27d5490d..796a1cf7 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -24,7 +24,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsutEFGST])/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -37,6 +37,12 @@ function ANSIEscapeParser(options) { this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, ''); this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25); this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80); + this.breakWidth = this.termWidth; + // toNumber takes care of null, undefined etc as well. + let artWidth = _.toNumber(options.artWidth); + if(!(_.isNaN(artWidth)) && artWidth > 0 && artWidth < this.breakWidth) { + this.breakWidth = options.artWidth; + } this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default'); this.row = Math.min(options?.startRow ?? 1, this.termHeight); @@ -90,8 +96,8 @@ function ANSIEscapeParser(options) { switch (charCode) { case CR: - self.emit('literal', text.slice(start, pos)); - start = pos; + self.emit('literal', text.slice(start, pos + 1)); + start = pos + 1; self.column = 1; @@ -105,8 +111,8 @@ function ANSIEscapeParser(options) { self.column = 1; } - self.emit('literal', text.slice(start, pos)); - start = pos; + self.emit('literal', text.slice(start, pos + 1)); + start = pos + 1; self.row += 1; @@ -114,13 +120,16 @@ function ANSIEscapeParser(options) { break; default: - if (self.column === self.termWidth) { + if (self.column === self.breakWidth) { self.emit('literal', text.slice(start, pos + 1)); start = pos + 1; + // If we hit breakWidth before termWidth then we need to force the terminal to go to the next line. + if(self.column < self.termWidth) { + self.emit('literal', '\r\n'); + } self.column = 1; self.row += 1; - self.positionUpdated(); } else { self.column += 1; @@ -135,7 +144,7 @@ function ANSIEscapeParser(options) { // // Finalize this chunk // - if (self.column > self.termWidth) { + if (self.column > self.breakWidth) { self.column = 1; self.row += 1; @@ -222,7 +231,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsutEFGST])/g, // eslint-disable-line no-control-regex stop: false, }; }; diff --git a/core/art.js b/core/art.js index 5e46591c..2145f5ad 100644 --- a/core/art.js +++ b/core/art.js @@ -41,11 +41,21 @@ const SUPPORTED_ART_TYPES = { }; function getFontNameFromSAUCE(sauce) { - if (sauce.Character) { + if (sauce && sauce.Character) { return sauce.Character.fontName; } } +function getWidthFromSAUCE(sauce) { + if (sauce && sauce.Character) { + let sauceWidth = _.toNumber(sauce.Character.characterWidth); + if(!(_.isNaN(sauceWidth)) && sauceWidth > 0) { + return sauceWidth; + } + } + return null; +} + function sliceAtEOF(data, eofMarker) { let eof = data.length; const stopPos = Math.max(data.length - 256, 0); // 256 = 2 * sizeof(SAUCE) @@ -274,6 +284,7 @@ function display(client, art, options, cb) { mciReplaceChar: options.mciReplaceChar, termHeight: client.term.termHeight, termWidth: client.term.termWidth, + artWidth: getWidthFromSAUCE(options.sauce), trailingLF: options.trailingLF, startRow: options.startRow, }); diff --git a/core/client_connections.js b/core/client_connections.js index 3dbf0e45..447669c2 100644 --- a/core/client_connections.js +++ b/core/client_connections.js @@ -87,7 +87,7 @@ function getActiveConnectionList( // entry.text = ac.user?.username || 'N/A'; entry.userName = ac.user?.username || 'N/A'; - entry.realName = ac.user?.getProperty(UserProps.RealName) || 'N/A'; + entry.realName = ac.user?.realName(false) || 'N/A'; entry.location = ac.user?.getProperty(UserProps.Location) || 'N/A'; entry.affils = entry.affiliation = ac.user?.getProperty(UserProps.Affiliations) || 'N/A'; diff --git a/core/config_default.js b/core/config_default.js index 6be17ec9..7421afc7 100644 --- a/core/config_default.js +++ b/core/config_default.js @@ -188,22 +188,15 @@ module.exports = () => { // // 1 - Generate a Private Key (PK): // Currently ENiGMA 1/2 requires a PKCS#1 PEM formatted PK. - // To generate a secure PK, issue the following command: - // - // > openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \ - // -pkeyopt rsa_keygen_pubexp:65537 | openssl rsa \ - // -out ./config/security/ssh_private_key.pem -aes128 - // - // (The above is a more modern equivalent of the following): - // > openssl genrsa -aes128 -out ./config/security/ssh_private_key.pem 2048 + // For information on generating a key, see: + // https://nuskooler.github.io/enigma-bbs/servers/loginservers/ssh.html#generate-a-ssh-private-key // // 2 - Set 'privateKeyPass' to the password you used in step #1 // // 3 - Finally, set 'enabled' to 'true' // // Additional reading: - // - https://blog.sleeplessbeastie.eu/2017/12/28/how-to-generate-private-key/ - // - https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b + // - https://nuskooler.github.io/enigma-bbs/servers/loginservers/ssh.html // privateKeyPem: paths.join( __dirname, @@ -222,14 +215,18 @@ module.exports = () => { // algorithms: { kex: [ + 'curve25519-sha256', + 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521', 'diffie-hellman-group14-sha1', 'diffie-hellman-group1-sha1', - // Group exchange not currnetly supported - // 'diffie-hellman-group-exchange-sha256', - // 'diffie-hellman-group-exchange-sha1', + 'curve25519-sha256', + 'curve25519-sha256@libssh.org', + 'ecdh-sha2-nistp256', + 'ecdh-sha2-nistp384', + 'ecdh-sha2-nistp521', ], cipher: [ 'aes128-ctr', @@ -242,12 +239,7 @@ module.exports = () => { 'aes256-cbc', 'aes192-cbc', 'aes128-cbc', - 'blowfish-cbc', '3des-cbc', - 'arcfour256', - 'arcfour128', - 'cast128-cbc', - 'arcfour', ], hmac: [ 'hmac-sha2-256', diff --git a/core/door.js b/core/door.js index fde6c2b7..6c974407 100644 --- a/core/door.js +++ b/core/door.js @@ -57,8 +57,10 @@ module.exports = class Door { run(exeInfo, cb) { this.encoding = (exeInfo.encoding || 'cp437').toLowerCase(); - if ('socket' === this.io && !this.sockServer) { - return cb(Errors.UnexpectedState('Socket server is not running')); + if ('socket' === this.io) { + if(!this.sockServer) { + return cb(Errors.UnexpectedState('Socket server is not running')); + } } else if ('stdio' !== this.io) { return cb(Errors.Invalid(`"${this.io}" is not a valid io type!`)); } diff --git a/core/fse.js b/core/fse.js index c5413989..29865c85 100644 --- a/core/fse.js +++ b/core/fse.js @@ -167,6 +167,7 @@ exports.FullScreenEditorModule = var newFocusViewId; if (errMsgView) { if (err) { + errMsgView.clearText(); errMsgView.setText(err.message); if (MciViewIds.header.subject === err.view.getId()) { @@ -183,6 +184,13 @@ exports.FullScreenEditorModule = return cb(null); }, editModeEscPressed: function (formData, extraArgs, cb) { + const errMsgView = self.viewControllers.header.getView( + MciViewIds.header.errorMsg + ); + if (errMsgView) { + errMsgView.clearText(); + } + self.footerMode = 'editor' === self.footerMode ? 'editorMenu' : 'editor'; @@ -982,11 +990,7 @@ exports.FullScreenEditorModule = const area = getMessageAreaByTag(self.messageAreaTag); if (fromView !== undefined) { if (area && area.realNames) { - fromView.setText( - self.client.user.properties[ - UserProps.RealName - ] || self.client.user.username - ); + fromView.setText(self.client.user.realName()); } else { fromView.setText(self.client.user.username); } @@ -1054,7 +1058,7 @@ exports.FullScreenEditorModule = posView.setText( _.padStart(String(pos.row + 1), 2, '0') + ',' + - _.padEnd(String(pos.col + 1), 2, '0') + _.padStart(String(pos.col + 1), 2, '0') ); this.client.term.rawWrite(ansi.restorePos()); } diff --git a/core/ftn_address.js b/core/ftn_address.js index 36ed3400..ec383c7e 100644 --- a/core/ftn_address.js +++ b/core/ftn_address.js @@ -135,7 +135,7 @@ module.exports = class Address { static fromString(addrStr) { const m = FTN_ADDRESS_REGEXP.exec(addrStr); - if (m) { + if (m && m[2] && m[3]) { // start with a 2D let addr = { net: parseInt(m[2]), diff --git a/core/message.js b/core/message.js index 6a063802..b9f84abd 100644 --- a/core/message.js +++ b/core/message.js @@ -763,6 +763,11 @@ module.exports = class Message { } persist(cb) { + const containsNonWhitespaceCharacterRegEx = /\S/; + if (!containsNonWhitespaceCharacterRegEx.test(this.message)) { + return cb(Errors.Invalid('Empty message')); + } + if (!this.isValid()) { return cb(Errors.Invalid('Cannot persist invalid message!')); } diff --git a/core/msg_area_post_fse.js b/core/msg_area_post_fse.js index 9eacd1f5..91e8f6d3 100644 --- a/core/msg_area_post_fse.js +++ b/core/msg_area_post_fse.js @@ -14,6 +14,39 @@ exports.moduleInfo = { author: 'NuSkooler', }; +const MciViewIds = { + header: { + from: 1, + to: 2, + subject: 3, + errorMsg: 4, + modTimestamp: 5, + msgNum: 6, + msgTotal: 7, + + customRangeStart: 10, // 10+ = customs + }, + + body: { + message: 1, + }, + + // :TODO: quote builder MCIs - remove all magic #'s + + // :TODO: consolidate all footer MCI's - remove all magic #'s + ViewModeFooter: { + MsgNum: 6, + MsgTotal: 7, + // :TODO: Just use custom ranges + }, + + quoteBuilder: { + quotedMsg: 1, + // 2 NYI + quoteLines: 3, + }, +}; + exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule { constructor(options) { super(options); @@ -42,19 +75,25 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule { ], function complete(err) { if (err) { - // :TODO:... sooooo now what? - } else { - // note: not logging 'from' here as it's part of client.log.xxxx() - self.client.log.info( - { - to: msg.toUserName, - subject: msg.subject, - uuid: msg.messageUuid, - }, - `User "${self.client.user.username}" posted message to "${msg.toUserName}" (${msg.areaTag})` + const errMsgView = self.viewControllers.header.getView( + MciViewIds.header.errorMsg ); + if (errMsgView) { + errMsgView.setText(err.message); + } + return cb(err); } + // note: not logging 'from' here as it's part of client.log.xxxx() + self.client.log.info( + { + to: msg.toUserName, + subject: msg.subject, + uuid: msg.messageUuid, + }, + `User "${self.client.user.username}" posted message to "${msg.toUserName}" (${msg.areaTag})` + ); + return self.nextMenu(cb); } ); diff --git a/core/my_messages.js b/core/my_messages.js index 0f1fe620..2d5d0c32 100644 --- a/core/my_messages.js +++ b/core/my_messages.js @@ -21,10 +21,7 @@ exports.getModule = class MyMessagesModule extends MenuModule { initSequence() { const filter = { - toUserName: [ - this.client.user.username, - this.client.user.getProperty(UserProps.RealName), - ], + toUserName: [this.client.user.username, this.client.user.realName()], sort: 'modTimestamp', resultType: 'messageList', limit: 1024 * 16, // we want some sort of limit... diff --git a/core/nua.js b/core/nua.js index 4f6f355d..9eabc232 100644 --- a/core/nua.js +++ b/core/nua.js @@ -13,6 +13,7 @@ const UserProps = require('./user_property.js'); // deps const _ = require('lodash'); +const moment = require('moment'); exports.moduleInfo = { name: 'NUA', @@ -95,15 +96,15 @@ exports.getModule = class NewUserAppModule extends MenuModule { areaTag = areaTag || ''; newUser.properties = { - [UserProps.RealName]: formData.value.realName, + [UserProps.RealName]: formData.value.realName || '', [UserProps.Birthdate]: getISOTimestampString( - formData.value.birthdate + formData.value.birthdate || moment() ), - [UserProps.Sex]: formData.value.sex, - [UserProps.Location]: formData.value.location, - [UserProps.Affiliations]: formData.value.affils, - [UserProps.EmailAddress]: formData.value.email, - [UserProps.WebAddress]: formData.value.web, + [UserProps.Sex]: formData.value.sex || '', + [UserProps.Location]: formData.value.location || '', + [UserProps.Affiliations]: formData.value.affils || '', + [UserProps.EmailAddress]: formData.value.email || '', + [UserProps.WebAddress]: formData.value.web || '', [UserProps.AccountCreated]: getISOTimestampString(), [UserProps.MessageConfTag]: confTag, diff --git a/core/system_view_validate.js b/core/system_view_validate.js index 61c52a52..8b4115af 100644 --- a/core/system_view_validate.js +++ b/core/system_view_validate.js @@ -21,8 +21,10 @@ exports.validateEmailAvail = validateEmailAvail; exports.validateBirthdate = validateBirthdate; exports.validatePasswordSpec = validatePasswordSpec; +const emptyFieldError = () => new Error('Field cannot be empty'); + function validateNonEmpty(data, cb) { - return cb(data && data.length > 0 ? null : new Error('Field cannot be empty')); + return cb(data && data.length > 0 ? null : emptyFieldError); } function validateMessageSubject(data, cb) { @@ -91,7 +93,11 @@ function validateGeneralMailAddressedTo(data, cb) { // :TODO: remove hard-coded FTN check here. We need a decent way to register global supported flavors with modules. const addressedToInfo = getAddressedToInfo(data); - if (Message.AddressFlavor.FTN === addressedToInfo.flavor) { + if (addressedToInfo.name.length === 0) { + return cb(emptyFieldError()); + } + + if (Message.AddressFlavor.Local !== addressedToInfo.flavor) { return cb(null); } diff --git a/core/text_view.js b/core/text_view.js index 74b622ba..2cb19040 100644 --- a/core/text_view.js +++ b/core/text_view.js @@ -179,6 +179,10 @@ TextView.prototype.setText = function (text, redraw) { }; TextView.prototype.clearText = function () { + if (this.text) { + this.setText(this.fillChar.repeat(this.text.length)); + } + this.setText(''); }; diff --git a/core/user.js b/core/user.js index 58cf69d9..125850bf 100644 --- a/core/user.js +++ b/core/user.js @@ -124,12 +124,29 @@ module.exports = class User { return isMember; } + realName(withUsernameFallback = true) { + const realName = this.getProperty(UserProps.RealName); + if (realName) { + return realName; + } + if (withUsernameFallback) { + return this.username; + } + } + getSanitizedName(type = 'username') { - const name = - 'real' === type ? this.getProperty(UserProps.RealName) : this.username; + const name = 'real' === type ? this.realName(true) : this.username; return sanatizeFilename(name) || `user${this.userId.toString()}`; } + emailAddress() { + const email = this.getProperty(UserProps.EmailAddress); + if (email) { + const realName = this.realName(false); + return realName ? `${realName} <${email}>` : email; + } + } + isAvailable() { return (this.statusFlags & User.StatusFlags.NotAvailable) == 0; } diff --git a/core/user_2fa_otp_web_register.js b/core/user_2fa_otp_web_register.js index edae1d5b..2d04280c 100644 --- a/core/user_2fa_otp_web_register.js +++ b/core/user_2fa_otp_web_register.js @@ -93,9 +93,7 @@ module.exports = class User2FA_OTPWebRegister { } const message = { - to: `${ - user.getProperty(UserProps.RealName) || user.username - } <${user.getProperty(UserProps.EmailAddress)}>`, + to: user.emailAddress(), // from will be filled in subject: '2-Factor Authentication Registration', text: textTemplate, diff --git a/core/user_config.js b/core/user_config.js index a612e95c..b597ac4d 100644 --- a/core/user_config.js +++ b/core/user_config.js @@ -115,15 +115,15 @@ exports.getModule = class UserConfigModule extends MenuModule { formData = _.clone(formData); const newProperties = { - [UserProps.RealName]: formData.value.realName, + [UserProps.RealName]: formData.value.realName || '', [UserProps.Birthdate]: getISOTimestampString( - formData.value.birthdate + formData.value.birthdate || moment() ), - [UserProps.Sex]: formData.value.sex, - [UserProps.Location]: formData.value.location, - [UserProps.Affiliations]: formData.value.affils, - [UserProps.EmailAddress]: formData.value.email, - [UserProps.WebAddress]: formData.value.web, + [UserProps.Sex]: formData.value.sex || '', + [UserProps.Location]: formData.value.location || '', + [UserProps.Affiliations]: formData.value.affils || '', + [UserProps.EmailAddress]: formData.value.email || '', + [UserProps.WebAddress]: formData.value.web || '', [UserProps.TermHeight]: formData.value.termHeight.toString(), [UserProps.ThemeId]: self.availThemeInfo[formData.value.theme].themeId, @@ -233,11 +233,7 @@ exports.getModule = class UserConfigModule extends MenuModule { function populateViews(callback) { const user = self.client.user; - self.setViewText( - 'menu', - MciCodeIds.RealName, - user.properties[UserProps.RealName] - ); + self.setViewText('menu', MciCodeIds.RealName, user.realName(false) || ''); self.setViewText( 'menu', MciCodeIds.BirthDate, diff --git a/core/web_password_reset.js b/core/web_password_reset.js index dbc4f88d..f5cf73c7 100644 --- a/core/web_password_reset.js +++ b/core/web_password_reset.js @@ -143,9 +143,7 @@ class WebPasswordReset { } const message = { - to: `${user.properties[UserProps.RealName] || user.username} <${ - user.properties[UserProps.EmailAddress] - }>`, + to: user.emailAddress(), // from will be filled in subject: 'Forgot Password', text: textTemplate, diff --git a/core/wfc.js b/core/wfc.js index 749593ab..41a258b2 100644 --- a/core/wfc.js +++ b/core/wfc.js @@ -506,9 +506,7 @@ exports.getModule = class WaitingForCallerModule extends MenuModule { // Current currentUserName: this.client.user.username, - currentUserRealName: - this.client.user.getProperty(UserProps.RealName) || - this.client.user.username, + currentUserRealName: this.client.user.realName(false) || 'N/A', availIndicator: availIndicator, visIndicator: visIndicator, lastLoginUserName: lastLoginStats.userName, diff --git a/docker/Dockerfile b/docker/Dockerfile index b53c72df..c09b7a5c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14-buster-slim +FROM node:18-buster-slim LABEL maintainer="dave@force9.org" @@ -21,8 +21,10 @@ RUN apt-get update \ lhasa \ unrar-free \ p7zip-full \ + dos2unix \ + && npm install -g npm@latest \ && npm install -g pm2 \ - && cd /enigma-bbs && npm install --only=production \ + && cd /enigma-bbs && npm install \ && pm2 start main.js \ && mkdir -p /enigma-bbs-pre/art \ && mkdir /enigma-bbs-pre/mods \ @@ -37,6 +39,7 @@ RUN apt-get update \ # sexyz COPY docker/bin/sexyz /usr/local/bin +RUN dos2unix /enigma-bbs/docker/bin/docker-entrypoint.sh && apt-get remove dos2unix -y RUN chmod +x /enigma-bbs/docker/bin/docker-entrypoint.sh # enigma storage mounts diff --git a/docs/Gemfile b/docs/Gemfile index ec96435d..ed87567f 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -30,3 +30,4 @@ end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem "webrick" \ No newline at end of file diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index de223cb6..8176ae45 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -6,8 +6,8 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) colorator (1.1.0) concurrent-ruby (1.2.2) cssminify2 (2.0.1) @@ -19,14 +19,14 @@ GEM ffi (1.15.5) forwardable-extended (2.6.0) gemoji (3.0.1) - html-pipeline (2.14.0) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) htmlcompressor (0.4.0) http_parser.rb (0.8.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - jekyll (4.2.1) + jekyll (4.2.2) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -49,7 +49,7 @@ GEM uglifier (~> 4.1) jekyll-relative-links (0.6.1) jekyll (>= 3.3, < 5.0) - jekyll-sass-converter (2.1.0) + jekyll-sass-converter (2.2.0) sassc (> 2.0.1, < 3.0) jekyll-seo-tag (2.7.1) jekyll (>= 3.8, < 5.0) @@ -64,30 +64,32 @@ GEM gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - json (2.6.1) + json (2.6.3) json-minify (0.0.3) json (> 0) - kramdown (2.3.1) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) + liquid (4.0.4) + listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.4.0) minitest (5.19.0) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.15.4-aarch64-linux) + racc (~> 1.4) + nokogiri (1.15.4-x86_64-linux) racc (~> 1.4) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.6) - racc (1.6.2) - rb-fsevent (0.11.0) + public_suffix (5.0.3) + racc (1.7.1) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) - rouge (3.28.0) + rexml (3.2.6) + rouge (3.30.0) safe_yaml (1.0.5) sassc (2.4.0) ffi (~> 1.9) @@ -98,8 +100,10 @@ GEM uglifier (4.2.0) execjs (>= 0.3.0, < 3) unicode-display_width (1.8.0) + webrick (1.8.1) PLATFORMS + aarch64-linux x86_64-linux DEPENDENCIES @@ -111,6 +115,7 @@ DEPENDENCIES jekyll-theme-hacker (~> 0.2.0) jemoji (~> 0.12.0) tzinfo-data + webrick BUNDLED WITH - 2.3.5 + 2.4.19 diff --git a/docs/_config.yml b/docs/_config.yml index 8520d124..7d70ca43 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -54,6 +54,7 @@ collections: - installation/network.md - installation/testing.md - installation/production.md + - installation/development.md - configuration/creating-config.md - configuration/sysop-setup.md - configuration/config-files.md @@ -130,4 +131,5 @@ collections: - admin/oputil.md - admin/updating.md - troubleshooting/monitoring-logs.md + - troubleshooting/ssh-troubleshooting.md diff --git a/docs/_docs/filebase/tic-support.md b/docs/_docs/filebase/tic-support.md index e1d01ffb..9639b5b7 100644 --- a/docs/_docs/filebase/tic-support.md +++ b/docs/_docs/filebase/tic-support.md @@ -3,7 +3,7 @@ layout: page title: TIC Support --- ## TIC Support -ENiGMA½ supports FidoNet-Style TIC file attachments by mapping TIC areas to local file areas. +ENiGMA½ supports FidoNet-Style TIC file attachments by mapping external TIC area tags to local file areas. Under a given node defined in the `ftn_bso` config section in `config.hjson` (see [BSO Import/Export](../messageareas/bso-import-export.md)), TIC configuration may be supplied: @@ -17,9 +17,9 @@ Under a given node defined in the `ftn_bso` config section in `config.hjson` (se packetPassword: mypass encoding: cp437 archiveType: zip - tic: { + tic: { // <--- General TIC config for 46:* password: TESTY-TEST - uploadBy: Agoranet TIC + uploadBy: AgoraNet TIC allowReplace: true } } @@ -29,7 +29,15 @@ Under a given node defined in the `ftn_bso` config section in `config.hjson` (se } ``` -You then need to configure the mapping between TIC areas you want to carry, and the file base area and storage tag for them to be tossed to. Optionally you can also add hashtags to the tossed files to assist users in searching for files: +Valid `tic` members: + +| Item | Required | Description | +|--------|---------------|------------------| +| `password` | :-1: | TIC packet password, if required | +| `uploadedBy` | :-1: | Sets the "uploaded by" field for TIC attachments, for example "AgoraNet TIC" | +| `allowReplace` | :-1: | Set to `true` to allow TIC attachments to replace each other. This is especially handy for things like weekly node list attachments | + +Next, we need to configure the mapping between TIC areas you want to carry, and the file base area (and, optionally, specific storage tag) for them to be tossed to. You can also add hashtags to the tossed files to assist users in searching for files: ```hjson ticAreas: { @@ -41,10 +49,22 @@ ticAreas: { } ``` -Multiple TIC areas can be mapped to a single file base area. + +> :information_source: Note that in the example above `agn_node` represents the **external** network area tag, usually represented in all caps. In this case, `AGN_NODE`. + +Valid `ticAreas` members under a given node mapping are as follows: + +| Item | Required | Description | +|--------|---------------|------------------| +| `areaTag` | :+1: | Specifies the local areaTag in which to place TIC attachments | +| `storageTag` | :-1: | Optionally, set a specific storageTag. If not set, the default for this area will be used. | +| `hashTags` | :-1: | One or more optional hash tags to assign TIC attachments in this area. | + + +💡 Multiple TIC areas can be mapped to a single file base area. ### Example Configuration -An example configuration linking file base areas, FTN BSO node configuration and TIC area configuration. +Example configuration fragments mapping file base areas, FTN BSO node configuration and TIC area configuration. ```hjson fileBase: { @@ -79,22 +99,24 @@ scannerTossers: { } } } + + ticAreas: { + // here we map AgoraNet AGN_NODE -> local msgNetworks file area + agn_node: { + areaTag: msgNetworks + storageTag: msg_network + hashTags: agoranet,nodelist + } + agn_info: { + areaTag: msgNetworks + storageTag: msg_network + hashTags: agoranet,infopack + } + } } } -ticAreas: { - agn_node: { - areaTag: msgNetworks - storageTag: msg_network - hashTags: agoranet,nodelist - } - agn_info: { - areaTag: msgNetworks - storageTag: msg_network - hashTags: agoranet,infopack - } -} ``` ## See Also diff --git a/docs/_docs/installation/development.md b/docs/_docs/installation/development.md new file mode 100644 index 00000000..ac27b528 --- /dev/null +++ b/docs/_docs/installation/development.md @@ -0,0 +1,38 @@ +--- +layout: page +title: Development Environment Setup +--- +_Note:_ This is only useful for people who are looking to contribute to the ENiGMA½ source base itself. Those that are just setting up a new BBS system do not need this section. + +The easiest way to get started with development on ENiGMA½ is via the pre-configured Visual Studio Code remote docker container environment. This setup will download and configure everything needed with minimal interaction. It also works cross-platform. + +* Install [Visual Studio Code](https://code.visualstudio.com/download) +* Install [Docker](https://docs.docker.com/engine/install/) +* Clone the [ENiGMA½](https://github.com/NuSkooler/enigma-bbs) repository. +* Choose "Open Folder" from Visual Studio Code and open the location where you cloned the repository. + +That's it! Visual Studio Code should prompt you for everything else that is needed, including some useful extensions for development. + +## Tasks + +Once it completes, there are a few tasks and run-configs that are useful. Open up the command pallete and search/choose "Tasks> Run Task". From there you can run the following tasks: + +### Start Jekyll (ENiGMA½ documentation server) + +This task will start the Jekyll server to perform local testing of changes to documentation. After running this task, open a browser to (http://localhost:4000/enigma-bbs/) to see the documentation. + +### (re)build Jekyll bundles + +When the image is created the Jekyll bundles are installed, so in general there shouldn't be much need to run this task. This is available however in case soemthing goes wrong or you are working on the Jekyll setup itself. + +### (re)build node modules + +Used to re-generate the node modules. Generally shouldn't be necessary unless something is broken or you are adding/changing versions of dependencies. + +### ENiGMA½ new configuration + +This task executes `oputil.js` in order to create a new BBS configuration (useful if you have just checked out the code and haven't setup any configuration yet.) + +## Run / Debug config + +There is also a default "Launch Program" config (hotkey access via F5 / Ctrl-Shift-D.) This will launch ENiGMA½. Once it has launched, access the system via telnet, port 8888 as usual. \ No newline at end of file diff --git a/docs/_docs/installation/docker.md b/docs/_docs/installation/docker.md index a6959b81..f2f7affa 100644 --- a/docs/_docs/installation/docker.md +++ b/docs/_docs/installation/docker.md @@ -71,5 +71,9 @@ Customising the Docker image is easy! 1. Clone the ENiGMA-BBS source. 2. Build the image ```bash -docker build -f ./docker/Dockerfile . +docker build -t enigmabbs -f ./docker/Dockerfile . +``` +3. Run the image +```bash +docker run -it -p 8888:8888 --name "ENiGMABBS" -v "$(pwd)/config:/enigma-bbs/config" -v "$(pwd)/db:/enigma-bbs/db" -v "$(pwd)/logs:/enigma-bbs/logs" -v "$(pwd)/filebase:/enigma-bbs/filebase" -v "$(pwd)/art:/enigma-bbs/art" -v "$(pwd)/mods:/enigma-bbs/mods" -v "$(pwd)/mail:/mail" enigmabbs ``` diff --git a/docs/_docs/servers/loginservers/ssh.md b/docs/_docs/servers/loginservers/ssh.md index 1d55299d..cee33a3d 100644 --- a/docs/_docs/servers/loginservers/ssh.md +++ b/docs/_docs/servers/loginservers/ssh.md @@ -3,9 +3,13 @@ layout: page title: SSH Server --- ## SSH Login Server + The ENiGMA½ SSH *login server* allows secure user logins over SSH (ssh://). +*Note:* If you run into any troubles during SSH setup, please see [Troubleshooting SSH](../../troubleshooting/ssh-troubleshooting.md) + ## Configuration + Entries available under `config.loginServers.ssh`: | Item | Required | Description | @@ -20,10 +24,8 @@ Entries available under `config.loginServers.ssh`: | `algorithms` | :-1: | Configuration block for SSH algorithms. Includes keys of `kex`, `cipher`, `hmac`, and `compress`. See the algorithms section in the [ssh2-streams](https://github.com/mscdex/ssh2-streams#ssh2stream-methods) documentation for details. For defaults set by ENiGMA½, see `core/config_default.js`. | `traceConnections` | :-1: | Set to `true` to enable full trace-level information on SSH connections. - * *IMPORTANT* With the `privateKeyPass` option set, make sure that you verify that the config file is not readable by other users! - ### Example Configuration ```hjson @@ -40,43 +42,94 @@ Entries available under `config.loginServers.ssh`: ``` ## Generate a SSH Private Key + To utilize the SSH server, an SSH Private Key (PK) will need generated. OpenSSH or (with some versions) OpenSSL can be used for this task: -### OpenSSH +### OpenSSH (Preferred) -```bash -ssh-keygen -m PEM -h -f config/ssh_private_key.pem +#### OpenSSH Install - Linux / Mac + +If it is not already available, install OpenSSH using the package manager of your choice (should be pre-installed on most distributions.) + +#### Running OpenSSH - Linux / Mac + +From the root directory of the Enigma BBS, run the following: + +```shell +mkdir -p config/security +ssh-keygen -t rsa -m PEM -h -f config/security/ssh_private_key.pem ``` +#### Windows Install - OpenSSH + +OpenSSH may already be installed, try running `ssh-keygen.exe`. If not, see this page: [Install OpenSSH for Windows](https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=gui) + +#### Running OpenSSH - Windows + +After installation, go to the root directory of your enigma project and run: + +```powershell +mkdir .\config\security -ErrorAction SilentlyContinue +ssh-keygen.exe -t rsa -m PEM -h -f .\config\security\ssh_private_key.pem +``` + +#### ssh-keygen options + Option descriptions: | Option | Description | |------|-------------| +| `-t rsa` | Use the RSA algorithm needed for the `ssh2` library | | `-m PEM` | Set the output format to `PEM`, compatible with the `ssh2` library | | `-h` | Generate a host key | | `-f config/ssh_private_key.pem` | Filename for the private key. Used in the `privateKeyPem` option in the configuration | When you execute the `ssh-keygen` command it will ask for a passphrase (and a confirmation.) This should then be used as the value for `privateKeyPass` in the configuration. - ### OpenSSL -If you do not have OpenSSH installed or if you have trouble with the above OpenSSH commands, using some versions for OpenSSL (before version 3) the following commands may work as well: +#### Open SSL Install - Linux / Mac +If not already installed, install via the `openssl` package on most package managers. -```bash -openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 | openssl rsa -out ./config/ssh_private_key.pem -aes128 +#### Open SSL Install - Windows + +```powershell +winget install -e --id ShiningLight.OpenSSL ``` -Or for even older OpenSSL versions: +#### Running OpenSSL -```bash +*Note:* Using `ssh-keygen` from OpenSSL is recommended where possible. If you have trouble with the above OpenSSH commands, using some versions for OpenSSL (before version 3) the following commands may work as well: + +#### Running OpenSSL - Linux / Mac + +Run the following from the root directory of Enigma + +```shell +mkdir -p config/security +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 | openssl rsa -out ./config/security/ssh_private_key.pem -aes128 +``` + +#### Running OpenSSL - Windows + +Run the following from the root directory of Enigma (note: you may need to specify the full path to openssl.exe if it isn't in your system path, on my system it was `C:\Program Files\OpenSSL-Win64\bin\openssl.exe`): + +```powershell +mkdir .\config\security -ErrorAction SilentlyContinue +openssl.exe genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 | openssl.exe rsa -out ./config/security/ssh_private_key.pem -aes128 +``` + +#### Running Older OpenSSL + +For older OpenSSL versions, the following command has been known to work: + +```shell openssl genrsa -aes128 -out ./config/ssh_private_key.pem 2048 ``` -Note that you may need `-3des` for very old implementations or SSH clients! - +*Note:* that you may need `-3des` for very old implementations or SSH clients! ## Prompt -The keyboard interactive prompt can be customized using a `SSHPMPT.ASC` art file. See [art](../../art/general.md) for more information on configuring. This prompt includes a `newUserNames` variable to show the list of allowed new user names (see `firstMenuNewUser` above.) See [mci](../../art/mci.md) for information about formatting this string. Note: Regardless of the content of the `SSHPMPT.ASC` file, the prompt is surrounded by "Access denied", a newline, the prompt, another newline, and then the string "\[username]'s password: ". This normally occurs after the first password prompt (no art is shown before the first password attempt is made.) +The keyboard interactive prompt can be customized using a `SSHPMPT.ASC` art file. See [art](../../art/general.md) for more information on configuring. This prompt includes a `newUserNames` variable to show the list of allowed new user names (see `firstMenuNewUser` above.) See [mci](../../art/mci.md) for information about formatting this string. Note: Regardless of the content of the `SSHPMPT.ASC` file, the prompt is surrounded by "Access denied", a newline, the prompt, another newline, and then the string "\[username]'s password: ". This normally occurs after the first password prompt (no art is shown before the first password attempt is made.) \ No newline at end of file diff --git a/docs/_docs/troubleshooting/ssh-troubleshooting.md b/docs/_docs/troubleshooting/ssh-troubleshooting.md new file mode 100644 index 00000000..4b59b4f5 --- /dev/null +++ b/docs/_docs/troubleshooting/ssh-troubleshooting.md @@ -0,0 +1,45 @@ +--- +layout: page +title: Troubleshooting SSH +--- + +Stuck with errors trying to get your SSH setup configured? See below for some common problems. Or as always, reach out to us by creating an [Issue](https://github.com/NuSkooler/enigma-bbs/issues) or start a [Discussion](https://github.com/NuSkooler/enigma-bbs/discussions) + +## No Such File or Directory + +***Symptom:*** +BBS not starting with an error similar to the following: + +```shell +Error initializing: Error: ENOENT: no such file or directory, open '/config/security/ssh_private_key.pem' +``` + +***Solution:*** +Several things can cause this: + +1. `ssh_private_key.pem` was installed to the wrong location. Make sure that it is in the `config/security` directory and has the name matching the error message. You can also change your `config.hjson` if you prefer to point to the location of the key file. +2. `ssh_private_key.pem` has the wrong file permissions. Verify that the file will be readable by the user that the BBS is running as. Because it is a cryptographic key however, we do recommend that access is restricted only to that user. + +## Error With Netrunner + +***Symptom:*** +Some ssh clients connect, but Netrunner (and other older clients) get a connection failed message and the following is in the log: + +```shell +"level":40,"error":"Handshake failed","code":2,"msg":"SSH connection error" +``` + +***Solution:*** + +The key was most likely not generated with the `-t rsa` option, and is using a newer algorithm that is not supported by Netrunner and similar clients. Regenerate the certificate with the `-t rsa` option. + +***Symptom:*** +Some ssh clients connect, but Netrunner (and other older clients) get a connection failed message and the following is in the log: + +```shell +"level":40,"error":"Group exchange not implemented for server","msg":"SSH connection error" +``` + +***Solution:*** + +Remove the following encryption protocols from your `config.hjson`: `diffie-hellman-group-exchange-sha256` and `diffie-hellman-group-exchange-sha1` \ No newline at end of file diff --git a/misc/menu_templates/private_mail.in.hjson b/misc/menu_templates/private_mail.in.hjson index e27314dc..2914a11a 100644 --- a/misc/menu_templates/private_mail.in.hjson +++ b/misc/menu_templates/private_mail.in.hjson @@ -69,7 +69,7 @@ } ] } - actionKeys: @reference: common.escToPrev + actionKeys: @reference:common.escToPrev } 1: { mci: { diff --git a/package.json b/package.json index 4a02825a..c2d1ea31 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,8 @@ "sanitize-filename": "^1.6.3", "sqlite3": "5.1.6", "sqlite3-trans": "1.3.0", - "ssh2": "1.11.0", - "systeminformation": "5.12.3", + "ssh2": "1.14.0", + "systeminformation": "5.21.7", "telnet-socket": "0.2.4", "temptmp": "^1.1.0", "uuid": "8.3.2", diff --git a/yarn.lock b/yarn.lock index 36f30704..7903a9bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -233,7 +233,7 @@ array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -asn1@^0.2.4: +asn1@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== @@ -455,7 +455,7 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== -cpu-features@~0.0.4: +cpu-features@~0.0.8: version "0.0.9" resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.9.tgz#5226b92f0f1c63122b0a3eb84cb8335a4de499fc" integrity sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ== @@ -1468,7 +1468,7 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" -nan@^2.14.0, nan@^2.16.0, nan@^2.17.0: +nan@^2.14.0, nan@^2.17.0: version "2.18.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== @@ -1986,16 +1986,16 @@ sqlite3@5.1.6: optionalDependencies: node-gyp "8.x" -ssh2@1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.11.0.tgz#ce60186216971e12f6deb553dcf82322498fe2e4" - integrity sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw== +ssh2@1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.14.0.tgz#8f68440e1b768b66942c9e4e4620b2725b3555bb" + integrity sha512-AqzD1UCqit8tbOKoj6ztDDi1ffJZ2rV2SwlgrVVrHPkV5vWqGJOVp5pmtj18PunkPJAuKQsnInyKV+/Nb2bUnA== dependencies: - asn1 "^0.2.4" + asn1 "^0.2.6" bcrypt-pbkdf "^1.0.2" optionalDependencies: - cpu-features "~0.0.4" - nan "^2.16.0" + cpu-features "~0.0.8" + nan "^2.17.0" ssri@^8.0.0, ssri@^8.0.1: version "8.0.1" @@ -2044,10 +2044,10 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -systeminformation@5.12.3: - version "5.12.3" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.12.3.tgz#1e6dc99070ace7d88b6a1dd465f4f3ce7d63901a" - integrity sha512-aPyTDzK/VjEheGEODprxFTMahIWTHGyWXxTsh9EOHjeekVltrIWrle4dOZouOlOYgtKM1pDoHkrR+IssgYCK/A== +systeminformation@5.21.7: + version "5.21.7" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.21.7.tgz#53ef75daaf5d756d015f4bb02e059126ccac74f2" + integrity sha512-K3LjnajrazTLTD61+87DFg8IXFk5ljx6nSBqB8pQLtC1UPivAjDtTYGPZ8jaBFxcesPaCOkvLRtBq+RFscrsLw== tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: version "6.2.0"