Compare commits

..

100 Commits

Author SHA1 Message Date
Bryan Ashby 6aa0c8679f
Merge pull request #520 from NuSkooler/feature/multiarch_docker
Feature/multiarch docker
2023-10-15 17:34:09 -06:00
Nathan Byrd 6b169de3ac Added missing change directory 2023-10-15 20:43:31 +00:00
Nathan Byrd 362f443290 Changed copy to directory location 2023-10-15 20:37:35 +00:00
Nathan Byrd 8a97f79d8c Removed old python package 2023-10-15 20:22:16 +00:00
Nathan Byrd f17bbe7c5a Updated docker support 2023-10-15 20:17:47 +00:00
Bryan Ashby 8c7df1fe75 Oops! 2023-10-12 20:41:59 -06:00
Bryan Ashby b00d5d07dd
Merge pull request #510 from NuSkooler/bugfix/scrolling_updates
Bugfix/scrolling updates
2023-10-12 20:36:54 -06:00
Bryan Ashby bbfef536e0
Merge pull request #489 from NuSkooler/bugfix/node_v20
Updated node-pty to support new node versions
2023-10-12 20:28:00 -06:00
Bryan Ashby 83636cb894 Sync up with master 2023-10-12 20:25:01 -06:00
Bryan Ashby ca985dd2cb Fix onExit(): emits a object containing exitCode and signal 2023-10-12 20:21:49 -06:00
Bryan Ashby b87ff51160 Merge branch 'bugfix/node_v20' of github.com:NuSkooler/enigma-bbs into bugfix/node_v20 2023-10-12 20:06:10 -06:00
Bryan Ashby 746233fa56 Updated lock 2023-10-12 20:05:45 -06:00
Bryan Ashby c84b3ee1c1
Merge pull request #515 from NuSkooler/bugfix/ssh_doc_updates
Updated the SSH version, config, and documentation
2023-10-11 19:43:21 -06:00
Nathan Byrd 270c09eb80 Added a sentence 2023-10-11 00:28:13 +00:00
Nathan Byrd f5b0a8bb60 Updated the SSH version, config, and documentation 2023-10-11 00:19:43 +00:00
Bryan Ashby 1a99153431
Merge pull request #514 from NuSkooler/bugfix/dev_env_python
Small bugfix on dev container startup to lock python version
2023-10-10 16:52:42 -06:00
Nathan Byrd 88a1b0ea69 locked python version to fix startup issue 2023-10-10 20:42:15 +00:00
Nathan Byrd 498c4a6082 Additional API changes 2023-10-10 19:43:58 +00:00
Nathan Byrd 577992bbe5 Changes to API for node-pty 2023-10-10 19:33:25 +00:00
Bryan Ashby db8bd2f80f
Merge pull request #511 from AnthonyHarwood/fse_err_msg_display_tweak
fse err msg display tweak
2023-09-29 12:01:05 -06:00
anthony 0161c22401 clear prior message; msg tweak for empty field 2023-09-28 11:05:10 -05:00
Nathan Byrd 402c5d0156 Merge branch 'master' into bugfix/scrolling_updates 2023-09-27 20:56:04 +00:00
anthony 5998144071 Merge branch 'bugfix/ftn_address_fromString' of https://github.com/AnthonyHarwood/enigma-bbs into bugfix/ftn_address_fromString 2023-09-27 15:52:00 -05:00
anthony d12b0789aa Added checks for undefined 2023-09-27 15:51:33 -05:00
Bryan Ashby a644672de2
Merge pull request #504 from AnthonyHarwood/disallow_posting_empty_msg
Disallow posting empty message
2023-09-26 17:17:18 -06:00
Bryan Ashby b0542cf51e
Merge pull request #508 from NuSkooler/bugfix/doors_socket
Fixed door logic per issue #506
2023-09-26 17:15:04 -06:00
Nathan Byrd 0ca22de6e9 Added back in unused escape codes for backwards compat 2023-09-26 22:02:49 +00:00
Nathan Byrd 394ac465c8 Merge remote-tracking branch 'origin/master' into bugfix/scrolling_updates 2023-09-26 21:50:04 +00:00
anthony 2b6494cfc9 Added checks for undefined 2023-09-26 16:14:10 -05:00
Nathan Byrd dddbad4541
Merge pull request #507 from AnthonyHarwood/bugfix/private_mail_menu_template
Removed extraneous space from private mail config template
2023-09-26 09:09:18 -05:00
Nathan Byrd 1d31b268aa Fixed door logic per issue #506 2023-09-26 14:00:05 +00:00
anthony 0e09ce491c Removed extraneous space from config template 2023-09-26 08:43:23 -05:00
anthony a6196c38ec Disallow posting empty message 2023-09-25 20:25:01 -05:00
Nathan Byrd 450ba65565 Fixed missing ansi codes causing formatting misses 2023-09-24 20:39:57 +00:00
Bryan Ashby fc107f1552
Merge pull request #503 from AnthonyHarwood/bugfix/TextView_clearText
Added code to clear the text
2023-09-22 15:57:19 -06:00
anthony f719499c65 Merge branch 'bugfix/TextView_clearText' of https://github.com/AnthonyHarwood/enigma-bbs into bugfix/TextView_clearText 2023-09-22 14:07:50 -05:00
anthony 75fa2026c0 Added code to clear the text 2023-09-22 14:05:16 -05:00
anthony 5180447e87 Added code to clear the text 2023-09-22 10:33:42 -05:00
Nathan Byrd 72a8546d74 Removed special handling of backspace and form feed 2023-09-22 01:18:38 +00:00
Bryan Ashby 19ee4b0553
Merge pull request #502 from NuSkooler/dependabot/npm_and_yarn/systeminformation-5.21.7
Bump systeminformation from 5.12.3 to 5.21.7
2023-09-21 11:50:26 -06:00
dependabot[bot] d3cf82b563
Bump systeminformation from 5.12.3 to 5.21.7
Bumps [systeminformation](https://github.com/sebhildebrandt/systeminformation) from 5.12.3 to 5.21.7.
- [Changelog](https://github.com/sebhildebrandt/systeminformation/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sebhildebrandt/systeminformation/compare/v5.12.3...v5.21.7)

---
updated-dependencies:
- dependency-name: systeminformation
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 17:24:32 +00:00
Nathan Byrd 79a3f4e1ce Handle deleting rows / columns 2023-09-20 23:52:34 +00:00
Nathan Byrd c0c262c971 Added insert and delete rows 2023-09-20 23:30:01 +00:00
Nathan Byrd 826db2d718 Added scrolling. 2023-09-20 23:00:32 +00:00
Nathan Byrd f3c9afc684 Added F and G 2023-09-20 21:40:59 +00:00
Nathan Byrd 372d84f572 Added backspace 2023-09-20 21:23:00 +00:00
Nathan Byrd 8029521399 changed handling of regex 2023-09-20 21:19:03 +00:00
Bryan Ashby 86049353a9
Merge pull request #501 from AnthonyHarwood/fse_cursor_position
fixed incorrect reporting of cursor position
2023-09-20 13:49:35 -06:00
Nathan Byrd 855fabe34d Added additional vt100 codes 2023-09-20 13:44:54 +00:00
anthony 6748b8966e fixed incorrect reporting of cursor position 2023-09-19 13:14:24 -05:00
Nathan Byrd 5712645299 Added additional characters that change position 2023-09-19 01:39:04 +00:00
Nathan Byrd a99b55abb5 Added support for some non-bracket escape sequences 2023-09-19 01:04:36 +00:00
Nathan Byrd d8f45f9147 Removed unused code 2023-09-18 23:36:27 +00:00
Nathan Byrd 08841528e2 Initial versions of new bansi codes 2023-09-18 23:35:44 +00:00
Bryan Ashby 5e120d1749
Merge pull request #498 from NuSkooler/bugfix/fix_art_length
Bugfix/fix art length
2023-09-05 22:50:57 -06:00
Nathan Byrd 06c3d100d7 Updated length for message list 2023-09-05 20:41:40 +00:00
Nathan Byrd 3adbb517e8 Updated art lengths to be 24 lines 2023-09-05 14:55:57 -05:00
Bryan Ashby bfb6a30860
Merge pull request #497 from NuSkooler/bugfix/github_action
Updated Docker action
2023-09-03 21:21:13 -06:00
Nathan Byrd e87e7ad277 Updated Docker action 2023-09-03 21:35:50 -05:00
Bryan Ashby e2bb00b756 Merge branch 'master' of github.com:NuSkooler/enigma-bbs 2023-09-03 11:02:29 -06:00
Bryan Ashby f4a90900ae TIC attachment doc updates 2023-09-03 11:01:43 -06:00
Bryan Ashby b307e791c3
Merge pull request #495 from NuSkooler/bugfix/pablodraw_ansi
Added additional escape control sequences for PabloDraw
2023-09-02 23:02:34 -06:00
Nathan Byrd 6fb77f3a5f Added additional escape control sequences 2023-09-02 23:50:41 -05:00
Bryan Ashby e8441408c0
Merge pull request #493 from NuSkooler/bugfix/Gemfile_lock
Updated Gemfile.lock to fix Github Actions failure
2023-08-30 15:36:48 -06:00
Nathan Byrd 60ee842c71 Updated Gemfile.lock to fix Github Actions failure 2023-08-30 19:14:20 +00:00
Bryan Ashby de918dd338
Merge pull request #480 from NuSkooler/479-nua-optional-fields
Make real name and other properties optional - pass 1
2023-08-29 20:43:14 -06:00
Bryan Ashby 367931cbd6
Merge branch 'master' into 479-nua-optional-fields 2023-08-29 20:43:00 -06:00
Bryan Ashby 1af4a0432b
Merge pull request #492 from NuSkooler/feature/docker_dev
Setup Docker development environment
2023-08-29 20:42:03 -06:00
Nathan Byrd 1bd328452b Added documentation for development setup 2023-08-28 15:39:19 -05:00
Nathan Byrd c2d7abc94e Removed browse task as it won't run without debugging 2023-08-28 14:17:47 -05:00
Nathan Byrd 602d693caf Added additional local extensions 2023-08-28 13:10:38 -05:00
Nathan Byrd 5f0a03638e Added some handy extensions and tasks 2023-08-28 13:08:54 -05:00
Nathan Byrd 8a6506c475 Additional changes needed to run 2023-08-27 19:04:17 -05:00
Bryan Ashby 97ae34a971 Quick fix on SAUCE 2023-08-27 17:31:35 -06:00
Bryan Ashby de339c9c52
Merge pull request #488 from NuSkooler/bugfix/art_width
Fix to display ANSI files with implicit new lines on wide terminals
2023-08-27 17:24:27 -06:00
Nathan Byrd 82c05c6e69 Merge branch 'master' into feature/docker_dev 2023-08-27 12:27:07 -05:00
Nathan Byrd 5ba1ca1464 Initial devcontainer setup 2023-08-27 12:25:36 -05:00
Bryan Ashby 3e1ec4477c
Merge pull request #491 from NuSkooler/feature/docker_dev
Docker Update
2023-08-26 21:11:26 -06:00
Nathan Byrd 13ae7789a5 No need to copy the ephemeral directories 2023-08-26 20:03:48 -05:00
Nathan Byrd 80dcc14a50 Small doc change to add running custom container 2023-08-26 11:05:39 -05:00
Nathan Byrd 1d00482b02 Updated node & added .dockerignore 2023-08-26 10:54:39 -05:00
Nathan Byrd adc2e1ba98 Fixed Dockerfile to run on Windows 2023-08-26 10:37:12 -05:00
Nathan Byrd 8758722b6b Fixed PR review comments 2023-08-25 18:34:45 -05:00
Nathan Byrd 4d70f07fde Updated node-pty to support new node versions 2023-08-25 15:59:35 -05:00
Nathan Byrd bf0bf83053 Small logic change to improve SAUCE handling 2023-08-25 14:38:32 -05:00
Nathan Byrd 7d46607ddc Updated the WHATSNEW to include info about the art change. 2023-08-25 14:33:28 -05:00
Nathan Byrd ccaaa71e23 Make the terminal go to new line if the width of the term is greater than the art width 2023-08-25 14:24:47 -05:00
Nathan Byrd 0b157ddd1b Changed ansi parser to use SAUCE width when available 2023-08-24 18:00:02 -05:00
Bryan Ashby 5a4563c799 PR feedback: Better handling of real name and email 2023-08-24 09:13:29 -06:00
Bryan Ashby 9205aaa9ee Merge branch 'master' of github.com:NuSkooler/enigma-bbs into 479-nua-optional-fields 2023-08-24 08:53:03 -06:00
Bryan Ashby b11ddfc382
Merge pull request #466 from NuSkooler/dependabot/npm_and_yarn/http-cache-semantics-4.1.1
Bump http-cache-semantics from 4.1.0 to 4.1.1
2023-08-23 21:49:25 -06:00
Bryan Ashby 993bdbe484
Merge branch 'master' into dependabot/npm_and_yarn/http-cache-semantics-4.1.1 2023-08-23 21:49:10 -06:00
Bryan Ashby 7b4910c415
Merge pull request #467 from NuSkooler/dependabot/npm_and_yarn/sqlite3-5.1.5
Bump sqlite3 from 5.0.11 to 5.1.5
2023-08-23 21:48:20 -06:00
Bryan Ashby 18678d3e4d
Merge branch 'master' into dependabot/npm_and_yarn/sqlite3-5.1.5 2023-08-23 21:47:47 -06:00
Bryan Ashby c87543c976 Update SQLite to latest 2023-08-23 21:45:08 -06:00
Bryan Ashby 25e4de5384
Merge pull request #484 from NuSkooler/dependabot/npm_and_yarn/word-wrap-1.2.5
Bump word-wrap from 1.2.3 to 1.2.5
2023-08-23 21:38:46 -06:00
dependabot[bot] 8e904f589d
Bump word-wrap from 1.2.3 to 1.2.5
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-24 01:03:03 +00:00
Bryan Ashby f99de19792 Make real name and other properties optional - pass 1 2023-07-24 12:17:47 -06:00
dependabot[bot] ddf412d34c
Bump sqlite3 from 5.0.11 to 5.1.5
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.11 to 5.1.5.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.11...v5.1.5)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 20:33:51 +00:00
dependabot[bot] 1bf7617404
Bump http-cache-semantics from 4.1.0 to 4.1.1
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 15:21:17 +00:00
83 changed files with 1202 additions and 805 deletions

9
.devcontainer/Dockerfile Normal file
View File

@ -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

View File

@ -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"]
}
}
}

12
.dockerignore Normal file
View File

@ -0,0 +1,12 @@
node_modules
package-lock.json
yarn.lock
filebase
db
drop
file_base
logs
mail
docs/_site
docs/.sass-cache

10
.gitattributes vendored
View File

@ -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

View File

@ -11,25 +11,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- 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
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true

3
.gitignore vendored
View File

@ -11,4 +11,5 @@ mail/
node_modules/
docs/_site/
docs/.sass-cache/
.vscode/
docs/.jekyll-cache/

6
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"recommendations": [
"ms-vscode-remote.remote-containers",
"laktak.hjson"
]
}

26
.vscode/tasks.json vendored Normal file
View File

@ -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"
}
]
}

View File

@ -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`

View File

@ -8,6 +8,9 @@ This document attempts to track **major** changes and additions in ENiGMA½. For
* Routes for the file base now default to `/_f/` prefixed instead of just `/f/`. If `/f/` is in your `config.hjson` you are encouraged to update it!
* 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.

View File

@ -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
}

View File

@ -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,

View File

@ -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]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/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);
@ -71,10 +77,25 @@ function ANSIEscapeParser(options) {
self.clearScreen = function () {
self.column = 1;
self.row = 1;
self.positionUpdated();
self.emit('clear screen');
};
self.positionUpdated = function () {
if(self.row > self.termHeight) {
if(this.savedPosition) {
this.savedPosition.row -= self.row - self.termHeight;
}
self.emit('scroll', self.row - self.termHeight);
self.row = self.termHeight;
}
else if(self.row < 1) {
if(this.savedPosition) {
this.savedPosition.row -= self.row - 1;
}
self.emit('scroll', -(self.row - 1));
self.row = 1;
}
self.emit('position update', self.row, self.column);
};
@ -90,8 +111,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 +126,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 +135,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 +159,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 +246,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]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/g, // eslint-disable-line no-control-regex
stop: false,
};
};
@ -262,9 +286,47 @@ function ANSIEscapeParser(options) {
opCode = match[2];
args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints
escape(opCode, args);
// Handle the case where there is no bracket
if(!(_.isNil(match[3]))) {
opCode = match[3];
args = [];
// no bracket
switch(opCode) {
// save cursor position
case '7':
escape('s', args);
break;
// restore cursor position
case '8':
escape('u', args);
break;
// scroll up
case 'D':
escape('S', args);
break;
// move to next line
case 'E':
// functonality is the same as ESC [ E
escape(opCode, args);
break;
// create a tab at current cursor position
case 'H':
literal('\t');
break;
// scroll down
case 'M':
escape('T', args);
break;
}
}
else {
escape(opCode, args);
}
//self.emit('chunk', match[0]);
self.emit('control', match[0], opCode, args);
}
} while (0 !== re.lastIndex);
@ -272,8 +334,8 @@ function ANSIEscapeParser(options) {
if (pos < buffer.length) {
var lastBit = buffer.slice(pos);
// :TODO: check for various ending LF's, not just DOS \r\n
if ('\r\n' === lastBit.slice(-2).toString()) {
// handles either \r\n or \n
if ('\n' === lastBit.slice(-1).toString()) {
switch (self.trailingLF) {
case 'default':
//
@ -281,14 +343,14 @@ function ANSIEscapeParser(options) {
// if we're going to end on termHeight
//
if (this.termHeight === self.row) {
lastBit = lastBit.slice(0, -2);
lastBit = lastBit.slice(0, -1);
}
break;
case 'omit':
case 'no':
case false:
lastBit = lastBit.slice(0, -2);
lastBit = lastBit.slice(0, -1);
break;
}
}
@ -299,48 +361,6 @@ function ANSIEscapeParser(options) {
self.emit('complete');
};
/*
self.parse = function(buffer, savedRe) {
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
// :TODO: move this to "constants" section @ top
var re = /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g;
var pos = 0;
var match;
var opCode;
var args;
// ignore anything past EOF marker, if any
buffer = buffer.split(String.fromCharCode(0x1a), 1)[0];
do {
pos = re.lastIndex;
match = re.exec(buffer);
if(null !== match) {
if(match.index > pos) {
parseMCI(buffer.slice(pos, match.index));
}
opCode = match[2];
args = getArgArray(match[1].split(';'));
escape(opCode, args);
self.emit('chunk', match[0]);
}
} while(0 !== re.lastIndex);
if(pos < buffer.length) {
parseMCI(buffer.slice(pos));
}
self.emit('complete');
};
*/
function escape(opCode, args) {
let arg;
@ -373,6 +393,37 @@ function ANSIEscapeParser(options) {
self.moveCursor(-arg, 0);
break;
// line feed
case 'E':
arg = isNaN(args[0]) ? 1 : args[0];
if(this.row + arg > this.termHeight) {
this.emit('scroll', arg - (this.termHeight - this.row));
self.moveCursor(0, this.termHeight);
}
else {
self.moveCursor(0, arg);
}
break;
// reverse line feed
case 'F':
arg = isNaN(args[0]) ? 1 : args[0];
if(this.row - arg < 1) {
this.emit('scroll', -(arg - this.row));
self.moveCursor(0, 1 - this.row);
}
else {
self.moveCursor(0, -arg);
}
break;
// absolute horizontal cursor position
case 'G':
arg = isNaN(args[0]) ? 1 : args[0];
self.column = Math.max(1, arg);
self.positionUpdated();
break;
case 'f': // horiz & vertical
case 'H': // cursor position
//self.row = args[0] || 1;
@ -383,14 +434,37 @@ function ANSIEscapeParser(options) {
self.positionUpdated();
break;
// save position
case 's':
self.saveCursorPosition();
// erase display/screen
case 'J':
if(isNaN(args[0]) || 0 === args[0]) {
self.emit('erase rows', self.row, self.termHeight);
}
else if (1 === args[0]) {
self.emit('erase rows', 1, self.row);
}
else if (2 === args[0]) {
self.clearScreen();
}
break;
// restore position
case 'u':
self.restoreCursorPosition();
// erase text in line
case 'K':
if(isNaN(args[0]) || 0 === args[0]) {
self.emit('erase columns', self.row, self.column, self.termWidth);
}
else if (1 === args[0]) {
self.emit('erase columns', self.row, 1, self.column);
}
else if (2 === args[0]) {
self.emit('erase columns', self.row, 1, self.termWidth);
}
break;
// insert line
case 'L':
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('insert line', self.row, arg);
break;
// set graphic rendition
@ -462,15 +536,52 @@ function ANSIEscapeParser(options) {
self.emit('sgr update', self.graphicRendition);
break; // m
// :TODO: s, u, K
// erase display/screen
case 'J':
// :TODO: Handle other 'J' types!
if (2 === args[0]) {
self.clearScreen();
}
// save position
case 's':
self.saveCursorPosition();
break;
// Scroll up
case 'S':
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('scroll', arg);
break;
// Scroll down
case 'T':
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('scroll', -arg);
break;
// restore position
case 'u':
self.restoreCursorPosition();
break;
// clear
case 'U':
self.clearScreen();
break;
// delete line
// TODO: how should we handle 'M'?
case 'Y':
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('delete line', self.row, arg);
break;
// back tab
case 'Z':
// calculate previous tabstop
self.column = Math.max( 1, self.column - (self.column % 8 || 8) );
self.positionUpdated();
break;
case '@':
// insert column(s)
arg = isNaN(args[0]) ? 1 : args[0];
self.emit('insert columns', self.row, self.column, arg);
break;
}
}
}

View File

@ -208,17 +208,17 @@ module.exports = class ArchiveUtil {
// pty.js doesn't currently give us a error when things fail,
// so we have this horrible, horrible hack:
let err;
proc.once('data', d => {
proc.onData(d => {
if (_.isString(d) && d.startsWith('execvp(3) failed.')) {
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
}
});
proc.once('exit', exitCode => {
proc.onExit(exitEvent => {
return cb(
exitCode
exitEvent.exitCode
? Errors.ExternalProcess(
`${action} failed with exit code: ${exitCode}`
`${action} failed with exit code: ${exitEvent.exitCode}`
)
: err
);
@ -358,10 +358,10 @@ module.exports = class ArchiveUtil {
output += data;
});
proc.once('exit', exitCode => {
if (exitCode) {
proc.onExit(exitEvent => {
if (exitEvent.exitCode) {
return cb(
Errors.ExternalProcess(`List failed with exit code: ${exitCode}`)
Errors.ExternalProcess(`List failed with exit code: ${exitEvent.exitCode}`)
);
}

View File

@ -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,
});
@ -305,6 +316,75 @@ function display(client, art, options, cb) {
}
});
// Remove any MCI's that are in erased rows
ansiParser.on('erase row', (startRow, endRow) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (mciInfo.position[0] >= startRow && mciInfo.position[0] <= endRow) {
delete mciMap[mapKey];
}
});
});
// Remove any MCI's that are in erased columns
ansiParser.on('erase columns', (row, startCol, endCol) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (
mciInfo.position[0] === row &&
mciInfo.position[1] >= startCol &&
mciInfo.position[1] <= endCol
) {
delete mciMap[mapKey];
}
});
});
ansiParser.on('insert columns', (row, startCol, numCols) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (mciInfo.position[0] === row && mciInfo.position[1] >= startCol) {
mciInfo.position[1] += numCols;
if(mciInfo.position[1] > client.term.termWidth) {
delete mciMap[mapKey];
}
}
});
});
// Clear the screen, removing any MCI's
ansiParser.on('clear screen', () => {
_.forEach(mciMap, (mciInfo, mapKey) => {
delete mciMap[mapKey];
});
});
ansiParser.on('scroll', (scrollY) => {
_.forEach(mciMap, (mciInfo) => {
mciInfo.position[0] -= scrollY;
});
});
ansiParser.on('insert line', (row, numLines) => {
_.forEach(mciMap, (mciInfo) => {
if (mciInfo.position[0] >= row) {
mciInfo.position[0] += numLines;
}
});
});
ansiParser.on('delete line', (row, numLines) => {
_.forEach(mciMap, (mciInfo, mapKey) => {
if (mciInfo.position[0] >= row) {
if(mciInfo.position[0] < row + numLines) {
// unlike scrolling, the rows are actually gone,
// so we need to delete any MCI's that are in them
delete mciMap[mapKey];
}
else {
mciInfo.position[0] -= numLines;
}
}
});
});
ansiParser.on('literal', literal => client.term.write(literal, false));
ansiParser.on('control', control => client.term.rawWrite(control));

View File

@ -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';

View File

@ -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',

View File

@ -57,8 +57,10 @@ module.exports = class Door {
run(exeInfo, cb) {
this.encoding = (exeInfo.encoding || 'cp437').toLowerCase();
if ('socket' === this.io && !this.sockServer) {
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!`));
}
@ -113,9 +115,10 @@ module.exports = class Door {
spawnOptions
);
prePty.once('exit', exitCode => {
prePty.onExit(exitEvent => {
const {exitCode, signal} = exitEvent;
this.client.log.info(
{ exitCode: exitCode },
{ exitCode, signal },
'Door pre-command exited'
);
return callback(null);
@ -165,7 +168,7 @@ module.exports = class Door {
this.doorPty.onData(this.doorDataHandler.bind(this));
this.doorPty.once('close', () => {
this.doorPty.onExit( (/*exitEvent*/) => {
return this.restoreIo(this.doorPty);
});
} else if ('socket' === this.io) {
@ -178,8 +181,9 @@ module.exports = class Door {
);
}
this.doorPty.once('exit', exitCode => {
this.client.log.info({ exitCode: exitCode }, 'Door exited');
this.doorPty.onExit(exitEvent => {
const {exitCode, signal} = exitEvent;
this.client.log.info({ exitCode, signal }, 'Door exited');
if (this.sockServer) {
this.sockServer.close();

View File

@ -167,17 +167,17 @@ class ScheduledEvent {
return cb(e);
}
proc.once('exit', exitCode => {
if (exitCode) {
proc.onExit(exitEvent => {
if (exitEvent.exitCode) {
Log.warn(
{ eventName: this.name, action: this.action, exitCode: exitCode },
{ eventName: this.name, action: this.action, exitCode: exitEvent.exitCode },
'Bad exit code while performing scheduled event action'
);
}
return cb(
exitCode
exitEvent.exitCode
? Errors.ExternalProcess(
`Bad exit code while performing scheduled event action: ${exitCode}`
`Bad exit code while performing scheduled event action: ${exitEvent.exitCode}`
)
: null
);

View File

@ -2,10 +2,8 @@
'use strict';
// ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule;
const ViewController = require('./view_controller.js').ViewController;
const { MenuModule, MenuFlags } = require('./menu_module.js');
const ansi = require('./ansi_term.js');
const theme = require('./theme.js');
const FileEntry = require('./file_entry.js');
const stringFormat = require('./string_format.js');
const FileArea = require('./file_base_area.js');
@ -77,6 +75,8 @@ exports.getModule = class FileAreaList extends MenuModule {
this.fileList = _.get(options, 'extraArgs.fileList');
this.lastFileNextExit = _.get(options, 'extraArgs.lastFileNextExit', true);
this.setMergedFlag(MenuFlags.NoHistory);
if (this.fileList) {
// we'll need to adjust position as well!
this.fileListPosition = 0;

View File

@ -2,7 +2,7 @@
'use strict';
// enigma-bbs
const MenuModule = require('./menu_module.js').MenuModule;
const { MenuModule, MenuFlags } = require('./menu_module.js');
const { getSortedAvailableFileAreas } = require('./file_base_area.js');
const StatLog = require('./stat_log.js');
const SysProps = require('./system_property.js');
@ -24,6 +24,8 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
constructor(options) {
super(options);
this.setMergedFlag(MenuFlags.NoHistory);
this.menuMethods = {
selectArea: (formData, extraArgs, cb) => {
const filterCriteria = {
@ -34,7 +36,7 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
extraArgs: {
filterCriteria: filterCriteria,
},
menuFlags: ['popParent', 'mergeFlags'],
menuFlags: [ MenuFlags.NoHistory ],
};
return this.gotoMenu(

View File

@ -2,10 +2,8 @@
'use strict';
// ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule;
const ViewController = require('./view_controller.js').ViewController;
const { MenuModule, MenuFlags } = require('./menu_module.js');
const DownloadQueue = require('./download_queue.js');
const theme = require('./theme.js');
const ansi = require('./ansi_term.js');
const Errors = require('./enig_error.js').Errors;
const FileAreaWeb = require('./file_area_web.js');
@ -38,6 +36,8 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
constructor(options) {
super(options);
this.setMergedFlag(MenuFlags.NoHistory);
this.dlQueue = new DownloadQueue(this.client);
if (_.has(options, 'lastMenuResult.sentFileIds')) {

View File

@ -121,7 +121,6 @@ exports.getModule = class FileBaseSearch extends MenuModule {
extraArgs: {
filterCriteria: filterCriteria,
},
menuFlags: ['popParent'],
};
return this.gotoMenu(

View File

@ -2,7 +2,7 @@
'use strict';
// ENiGMA½
const { MenuModule } = require('./menu_module.js');
const { MenuModule, MenuFlags } = require('./menu_module.js');
const FileEntry = require('./file_entry.js');
const FileArea = require('./file_base_area.js');
const { renderSubstr } = require('./string_util.js');
@ -65,6 +65,9 @@ const MciViewIds = {
exports.getModule = class FileBaseListExport extends MenuModule {
constructor(options) {
super(options);
this.setMergedFlag(MenuFlags.NoHistory);
this.config = Object.assign(
{},
_.get(options, 'menuConfig.config'),

View File

@ -2,10 +2,8 @@
'use strict';
// ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule;
const ViewController = require('./view_controller.js').ViewController;
const { MenuModule, MenuFlags } = require('./menu_module.js');
const DownloadQueue = require('./download_queue.js');
const theme = require('./theme.js');
const ansi = require('./ansi_term.js');
const Errors = require('./enig_error.js').Errors;
const FileAreaWeb = require('./file_area_web.js');
@ -40,6 +38,8 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
constructor(options) {
super(options);
this.setMergedFlag(MenuFlags.NoHistory);
this.dlQueue = new DownloadQueue(this.client);
this.menuMethods = {

View File

@ -485,13 +485,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
});
externalProc.once('close', () => {
return this.restorePipeAfterExternalProc();
});
externalProc.once('exit', exitCode => {
externalProc.onExit(exitEvent => {
const {exitCode, signal} = exitEvent;
this.client.log.debug(
{ cmd: cmd, args: args, exitCode: exitCode },
{ cmd: cmd, args: args, exitCode, signal },
'Process exited'
);

View File

@ -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());
}

View File

@ -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]),

View File

@ -20,6 +20,23 @@ const assert = require('assert');
const _ = require('lodash');
const iconvDecode = require('iconv-lite').decode;
const MenuFlags = {
// When leaving this menu to load/chain to another, remove this
// menu from history. In other words, the fallback from
// the next menu would *not* be this one, but the previous.
NoHistory: 'noHistory',
// Generally used in code only: Request that any flags from menu.hjson
// are merged in to the total set of flags vs overriding the default.
MergeFlags: 'mergeFlags',
// Forward this menu's 'extraArgs' to the next.
ForwardArgs: 'forwardArgs',
};
exports.MenuFlags = MenuFlags;
exports.MenuModule = class MenuModule extends PluginModule {
constructor(options) {
super(options);
@ -48,6 +65,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
});
}
setMergedFlag(flag) {
this.menuConfig.config.menuFlags.push(flag);
this.menuConfig.config.menuFlags = [...new Set([...this.menuConfig.config.menuFlags, MenuFlags.MergeFlags])];
}
static get InterruptTypes() {
return {
Never: 'never',

View File

@ -5,12 +5,12 @@
const loadMenu = require('./menu_util.js').loadMenu;
const { Errors, ErrorReasons } = require('./enig_error.js');
const { getResolvedSpec } = require('./menu_util.js');
const { MenuFlags } = require('./menu_module.js');
// deps
const _ = require('lodash');
const assert = require('assert');
// :TODO: Stack is backwards.... top should be most recent! :)
const bunyan = require('bunyan');
module.exports = class MenuStack {
constructor(client) {
@ -27,20 +27,12 @@ module.exports = class MenuStack {
}
peekPrev() {
if (this.stackSize > 1) {
return this.stack[this.stack.length - 2];
}
}
top() {
if (this.stackSize > 0) {
return this.stack[this.stack.length - 1];
}
}
get stackSize() {
return this.stack.length;
}
get currentModule() {
const top = this.top();
@ -81,7 +73,6 @@ module.exports = class MenuStack {
prev(cb) {
const menuResult = this.top().instance.getMenuResult();
// :TODO: leave() should really take a cb...
this.pop().instance.leave(); // leave & remove current
const previousModuleInfo = this.pop(); // get previous
@ -129,7 +120,7 @@ module.exports = class MenuStack {
client: self.client,
};
if (currentModuleInfo && currentModuleInfo.menuFlags.includes('forwardArgs')) {
if (currentModuleInfo && currentModuleInfo.menuFlags.includes(MenuFlags.ForwardArgs)) {
loadOpts.extraArgs = currentModuleInfo.extraArgs;
} else {
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
@ -138,7 +129,6 @@ module.exports = class MenuStack {
loadMenu(loadOpts, (err, modInst) => {
if (err) {
// :TODO: probably should just require a cb...
const errCb = cb || self.client.defaultHandlerMissingMod();
errCb(err);
} else {
@ -151,22 +141,6 @@ module.exports = class MenuStack {
return;
}
//
// Handle deprecated 'options' block by merging to config and warning user.
// :TODO: Remove in 0.0.10+
//
if (modInst.menuConfig.options) {
self.client.log.warn(
{ options: modInst.menuConfig.options },
'Use of "options" is deprecated. Move relevant members to "config" block! Support will be fully removed in future versions'
);
Object.assign(
modInst.menuConfig.config || {},
modInst.menuConfig.options
);
delete modInst.menuConfig.options;
}
//
// If menuFlags were supplied in menu.hjson, they should win over
// anything supplied in code.
@ -180,9 +154,9 @@ module.exports = class MenuStack {
// in code we can ask to merge in
if (
Array.isArray(options.menuFlags) &&
options.menuFlags.includes('mergeFlags')
options.menuFlags.includes(MenuFlags.MergeFlags)
) {
menuFlags = _.uniq(menuFlags.concat(options.menuFlags));
menuFlags = [...new Set(options.menuFlags)]; // make unique
}
}
@ -193,12 +167,8 @@ module.exports = class MenuStack {
currentModuleInfo.instance.leave();
if (currentModuleInfo.menuFlags.includes('noHistory')) {
this.pop();
}
if (menuFlags.includes('popParent')) {
this.pop().instance.leave(); // leave & remove current
if (currentModuleInfo.menuFlags.includes(MenuFlags.NoHistory)) {
this.pop().instance.leave(); // leave & remove current from stack
}
}
@ -214,6 +184,7 @@ module.exports = class MenuStack {
modInst.restoreSavedState(options.savedState);
}
if (self.client.log.level() <= bunyan.TRACE) {
const stackEntries = self.stack.map(stackEntry => {
let name = stackEntry.name;
if (stackEntry.instance.menuConfig.config.menuFlags.length > 0) {
@ -225,6 +196,7 @@ module.exports = class MenuStack {
});
self.client.log.trace({ stack: stackEntries }, 'Updated menu stack');
}
modInst.enter();

View File

@ -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!'));
}

View File

@ -113,7 +113,6 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
const returnNoResults = () => {
return this.gotoMenu(
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
{ menuFlags: ['popParent'] },
cb
);
};
@ -160,7 +159,6 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
messageList,
noUpdateLastReadId: true,
},
menuFlags: ['popParent'],
};
return this.gotoMenu(

View File

@ -2,7 +2,7 @@
'use strict';
// ENiGMA½
const { MenuModule } = require('./menu_module.js');
const { MenuModule, MenuFlags } = require('./menu_module.js');
const messageArea = require('./message_area.js');
const { Errors } = require('./enig_error.js');
const UserProps = require('./user_property.js');
@ -29,6 +29,9 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
constructor(options) {
super(options);
// always include noHistory flag
this.setMergedFlag(MenuFlags.NoHistory);
this.initList();
this.menuMethods = {
@ -49,7 +52,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
extraArgs: {
areaTag: area.areaTag,
},
menuFlags: ['popParent', 'noHistory'],
menuFlags: [ MenuFlags.NoHistory ],
};
return this.gotoMenu(

View File

@ -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,8 +75,15 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
],
function complete(err) {
if (err) {
// :TODO:... sooooo now what?
} else {
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(
{
@ -53,7 +93,6 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
},
`User "${self.client.user.username}" posted message to "${msg.toUserName}" (${msg.areaTag})`
);
}
return self.nextMenu(cb);
}

View File

@ -2,7 +2,7 @@
'use strict';
// ENiGMA½
const { MenuModule } = require('./menu_module.js');
const { MenuModule, MenuFlags } = require('./menu_module.js');
const messageArea = require('./message_area.js');
const { Errors } = require('./enig_error.js');
@ -26,6 +26,9 @@ exports.getModule = class MessageConfListModule extends MenuModule {
constructor(options) {
super(options);
// always include noHistory flag
this.setMergedFlag(MenuFlags.NoHistory);
this.initList();
this.menuMethods = {
@ -49,7 +52,7 @@ exports.getModule = class MessageConfListModule extends MenuModule {
extraArgs: {
confTag: conf.confTag,
},
menuFlags: ['popParent', 'noHistory'],
menuFlags: [ MenuFlags.NoHistory ],
};
return this.gotoMenu(

View File

@ -2,7 +2,7 @@
'use strict';
// ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule;
const { MenuModule, MenuFlags } = require('./menu_module');
const Message = require('./message.js');
const UserProps = require('./user_property.js');
const { filterMessageListByReadACS } = require('./message_area.js');
@ -16,14 +16,12 @@ exports.moduleInfo = {
exports.getModule = class MyMessagesModule extends MenuModule {
constructor(options) {
super(options);
this.setMergedFlag(MenuFlags.NoHistory);
}
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...
@ -49,7 +47,6 @@ exports.getModule = class MyMessagesModule extends MenuModule {
if (!this.messageList || 0 === this.messageList.length) {
return this.gotoMenu(
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
{ menuFlags: ['popParent'] }
);
}
@ -58,7 +55,6 @@ exports.getModule = class MyMessagesModule extends MenuModule {
messageList: this.messageList,
noUpdateLastReadId: true,
},
menuFlags: ['popParent'],
};
return this.gotoMenu(

View File

@ -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,

View File

@ -4,7 +4,6 @@
// ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule;
const Errors = require('../core/enig_error.js').Errors;
const ANSI = require('./ansi_term.js');
const Config = require('./config.js').get;
const { getMessageAreaByTag } = require('./message_area.js');
@ -21,6 +20,7 @@ exports.moduleInfo = {
exports.getModule = class ShowArtModule extends MenuModule {
constructor(options) {
super(options);
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), {
extraArgs: options.extraArgs,
});

View File

@ -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);
}

View File

@ -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('');
};

View File

@ -2,7 +2,7 @@
'use strict';
// enigma-bbs
const MenuModule = require('./menu_module.js').MenuModule;
const { MenuModule, MenuFlags } = require('./menu_module');
const stringFormat = require('./string_format.js');
const getSortedAvailableFileAreas =
require('./file_base_area.js').getSortedAvailableFileAreas;
@ -76,6 +76,8 @@ exports.getModule = class UploadModule extends MenuModule {
constructor(options) {
super(options);
this.setMergedFlag(MenuFlags.NoHistory);
this.interrupt = MenuModule.InterruptTypes.Never;
if (_.has(options, 'lastMenuResult.recvFilePaths')) {

View File

@ -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;
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -1,19 +1,25 @@
FROM node:14-buster-slim
FROM --platform=${BUILDPLATFORM:-linux/amd64} node:20-bookworm-slim
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETOS
ARG TARGETBRANCH
LABEL maintainer="dave@force9.org"
ENV NVM_DIR /root/.nvm
ENV DEBIAN_FRONTEND noninteractive
COPY . /enigma-bbs
# Do some installing! (and alot of cleaning up) keeping it in one step for less docker layers
# - if you need to debug i recommend to break the steps with individual RUNs)
# Just copy the package.json so it only needs to build once
COPY package.json /enigma-bbs/
# Install APT and NPM packages
RUN apt-get update \
&& apt-get install -y \
git \
curl \
build-essential \
python \
python3 \
libssl-dev \
lrzsz \
@ -21,8 +27,22 @@ RUN apt-get update \
lhasa \
unrar-free \
p7zip-full \
dos2unix \
&& npm set progress=false && npm config set depth 0 \
&& npm install -g npm@latest \
&& npm install -g pm2 \
&& cd /enigma-bbs && npm install --only=production \
&& cd /enigma-bbs && npm install
# Do this after npm install to avoid cache-miss on every code change
COPY . /enigma-bbs
# Then run post source copy steps that have to happen every time
RUN dos2unix /enigma-bbs/docker/bin/docker-entrypoint.sh \
&& apt-get remove dos2unix -y \
&& chmod +x /enigma-bbs/docker/bin/docker-entrypoint.sh \
&& cp -f /enigma-bbs/docker/bin/sexyz /usr/local/bin \
&& cd /enigma-bbs \
&& pm2 start main.js \
&& mkdir -p /enigma-bbs-pre/art \
&& mkdir /enigma-bbs-pre/mods \
@ -30,15 +50,11 @@ RUN apt-get update \
&& cp -rp art/* ../enigma-bbs-pre/art/ \
&& cp -rp mods/* ../enigma-bbs-pre/mods/ \
&& cp -rp config/* ../enigma-bbs-pre/config/ \
&& apt-get remove build-essential python python3 libssl-dev git curl -y \
&& apt-get remove build-essential python3 libssl-dev git curl -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& apt-get clean
# sexyz
COPY docker/bin/sexyz /usr/local/bin
RUN chmod +x /enigma-bbs/docker/bin/docker-entrypoint.sh
# enigma storage mounts
VOLUME /enigma-bbs/art
VOLUME /enigma-bbs/config

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -323,7 +323,7 @@ qwk-export arguments:
| Action | Description | Examples |
|-----------|-------------------|---------------------------------------|
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` |
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js mb import-areas /some/path/l33tnet.na` |
| `areafix` | Utility for sending AreaFix mails without logging into the system | |
| `qwk-dump` | Dump a QWK packet to stdout | `./oputil.js mb qwk-dump /path/to/XIBALBA.QWK` |
| `qwk-export` | Export messages to a QWK packet | `./oputil.js mb qwk-export /path/to/XIBALBA.QWK` |

View File

@ -59,13 +59,15 @@ The `config` block for a menu entry can contain common members as well as a per-
| `menuFlags` | An array of menu flag(s) controlling menu behavior. See **Menu Flags** below.
#### Menu Flags
The `menuFlags` field of a `config` block can change default behavior of a particular menu.
The `menuFlags` field of a `config` block can change default behavior of a particular menu:
| Flag | Description |
|------|-------------|
| `noHistory` | Prevents the menu from remaining in the menu stack / history. When this flag is set, when the **next** menu falls back, this menu will be skipped and the previous menu again displayed instead. Example: menuA -> menuB(noHistory) -> menuC: Exiting menuC returns the user to menuA. |
| `popParent` | When *this* menu is exited, fall back beyond the parent as well. Often used in combination with `noHistory`. |
| `forwardArgs` | If set, when the next menu is entered, forward any `extraArgs` arguments to *this* menu on to it. |
| `noHistory` | When leaving the current menu to load/chain to another, remove this menu from history. In other words, the fallback from the next menu would *not* be this one, but the previous. |
| `mergeFlags` | Generally used in code only: Request that any flags from `menu.hjson` |
| `forwardArgs` | Forward this menu's `extraArgs` to the next. |
> 💡 In JavaScript code, `MenuFlags` from `menu_module.js` contains constants for these flags.
## Forms

View File

@ -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,11 +99,9 @@ scannerTossers: {
}
}
}
}
}
ticAreas: {
// here we map AgoraNet AGN_NODE -> local msgNetworks file area
agn_node: {
areaTag: msgNetworks
storageTag: msg_network
@ -95,6 +113,10 @@ ticAreas: {
hashTags: agoranet,infopack
}
}
}
}
```
## See Also

View File

@ -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.

View File

@ -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
```

View File

@ -58,7 +58,6 @@ showFileBaseAreaArt: {
method: fileBaseArea
cls: true
pause: true
menuFlags: [ "popParent", "noHistory" ]
}
}
```

View File

@ -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,42 +42,93 @@ 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

View File

@ -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 '<path>/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`

View File

@ -388,7 +388,7 @@
art: FEMPTYQ
config: {
pause: true
menuFlags: [ "noHistory", "popParent" ]
menuFlags: [ "noHistory" ]
}
}
@ -779,7 +779,7 @@
art: FBNORES
config: {
pause: true
menuFlags: [ "noHistory", "popParent" ]
menuFlags: [ "noHistory" ]
}
}
@ -807,7 +807,7 @@
art: FBNORES
config: {
pause: true
menuFlags: [ "noHistory", "popParent" ]
menuFlags: [ "noHistory" ]
}
}
@ -852,7 +852,7 @@
art: ULNOAREA
config: {
pause: true
menuFlags: [ "noHistory", "popParent" ]
menuFlags: [ "noHistory" ]
}
}

View File

@ -776,7 +776,7 @@
key: confTag
pause: true
cls: true
menuFlags: [ "popParent", "noHistory" ]
menuFlags: [ "noHistory" ]
}
}
@ -794,7 +794,7 @@
key: areaTag
pause: true
cls: true
menuFlags: [ "popParent", "noHistory" ]
menuFlags: [ "noHistory" ]
}
}
}

View File

@ -50,17 +50,17 @@
"minimist": "^1.2.6",
"moment": "2.29.4",
"nntp-server": "3.1.0",
"node-pty": "0.10.1",
"node-pty": "1.0.0",
"nodemailer": "6.7.7",
"otplib": "11.0.1",
"qrcode-generator": "^1.4.4",
"rlogin": "^1.0.0",
"sane": "5.0.1",
"sanitize-filename": "^1.6.3",
"sqlite3": "5.0.11",
"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",

793
yarn.lock

File diff suppressed because it is too large Load Diff