open
https://gitlab.synchro.net/main/sbbs/-/issues/1233
### Background
A terminal session's effective terminal type is reconciled exactly once, in `logon()` (`src/sbbs3/logon.cpp:171-177`):
```c
const int manual_term = ANSI | RIP | PETSCII | UTF8; // Note: don't turn off NO_EXASCII flag (issue #923)
if ((useron.misc & AUTOTERM)
|| ((useron.misc & manual_term) && (useron.misc & manual_term) != (autoterm & manual_term))
|| ((autoterm & UTF8) && !(useron.misc & UTF8))) {
useron.misc &= ~manual_term;
useron.misc |= (AUTOTERM | (autoterm & ~NO_EXASCII));
}
```
That write is to memory only. The user record's terminal bits are therefore a *per-account seed* recording the terminal type last detected for that account by any connection — which is deliberate: with `AUTOTERM` off, the manual prompts in `exec/user_terminal.js:113-118` (and the `useredit`/`uedit` checkboxes) default from those stored bits, so a user who turns auto-detection off isn't asked "ANSI terminal? [no]" on an obviously-ANSI terminal.
### Problem
Nothing re-applies that reconciliation when the record is reloaded over `sbbs->useron` mid-session, and there are two paths that do exactly that:
1. **`js_user.cpp`** — the end of `js_user_set()` invalidates the cache
(`if (!user_is_guest(p->user)) p->cached = false;`), and `js_user_get()`
unconditionally calls `p->getuserdat()`, which `fgetuserdat()`s the record
into `p->user`. For the global session user object, `p->user` **is**
`&sbbs->useron` (`js_CreateUserObject(..., global_user=true)`,
`main.cpp:1540`). So any `user.*` property write followed by any `user.*`
property read replaces the live user struct with the on-disk record —
including whatever terminal type another node last seeded there.
2. **`change_user()`** (`src/sbbs3/str.cpp:1413`) — `;chuser` loads the target
user's record into `useron` on the sysop's existing terminal, so the session
inherits that account's saved terminal bits.
Observed symptom (reported by a sysop, VERT/CVS): log on with a RIP terminal while a second, non-RIP session is online; the non-RIP session begins auto-displaying the `msgscan` menu at the message-reading prompt — a menu `readmsgs.cpp` shows for RIP terminals only.
That particular symptom is fixed in 86f6584082, which moved the C++ terminal-capability tests off the record bits and onto
`Terminal::supports()` / `Terminal::charset()` (which re-derive from `sbbs->autoterm` in `Terminal::get_flags()`, `terminal.h:117-133`). What remains are the consumers that read the record's copy directly:
- `user.compare_ars("RIP")` (also `ANSI`/`PETSCII`/`CP437`/`UTF8`) from JS —
`js_chk_ar()` calls `p->getuserdat()` and then the library `chk_ar()`
(`userdat.c:2332`), which tests `user->misc & RIP`. (`js_user.cpp:1577`
already documents preferring `bbs.compare_ars()` in the terminal server,
which resolves to the terminal-aware `sbbs_t::chk_ar()`.)
- Stock JS reading `user.settings & USER_RIP` directly, e.g.
`exec/load/user_info_prompts.js:228`.
- `;chuser`, as above.
There is a second-order effect worth noting: after a reload, the session's `useron.misc` holds another connection's terminal bits, and the next
whole-word `putusermisc()` (e.g. `user.settings ^= USER_EXPERT` from any of the JS command shells) writes them straight back to the record. A session that never detected RIP can thus re-persist RIP as the account's seed.
### Proposal
Extract the `logon()` block into a member function and call it wherever the record is reloaded into `useron`:
```c
void sbbs_t::fixup_term_flags(); // declared in sbbs.h
```
Call sites:
- `logon()`, where the block is today.
- `change_user()` after the successful `getuserdat()`.
- `user_private_t::getuserdat()` in `js_user.cpp`, but **only** when the object
owns the live session user (`p->user != &p->storage`) and only in the
terminal server.
Plumbing note: `user_private_t::getuserdat()` (`js_user.cpp:39-49`) has no `JSContext*` and no `sbbs_t*`. Either pass the context in
(`p->getuserdat(cx)`) or apply the fixup in `js_user_get()` right after the call. Either way it needs the same "am I in the terminal server?" test the `USER_PROP_MISC` setter already open-codes at `js_user.cpp:656-670` (global has a `bbs` property that is a `js_bbs_class` instance ⇒ `JS_GetContextPrivate()` is the `sbbs_t*`); that is worth factoring into a small helper used by both.
### Explicitly not part of this
- **Do not call `update_terminal()` from the reload path.** It deletes and
replaces `sbbs->term` (`terminal.cpp:434`); `Terminal::flags()` warns "We
have potentially destructed ourselves now… use the new object", and
`inkey.cpp:79` carries a `// update_terminal(this); causes later crash`
comment. Fix up `useron.misc` only and let the existing lazy self-heal in
`Terminal::flags()` (`terminal.cpp:334-340`) swap the object at a safe point. - **Do not stop persisting the `manual_term` bits.** They are the seed
described above; not writing them would regress `user_terminal.js`'s manual
prompts and the user-editor checkboxes.
- **The whole-word `misc` write is a separate issue.** `putusermisc()`
(`js_user.cpp:654`, `execmisc.cpp:1549`, `listfile.cpp:533`) writes all 32
bits from a node's private copy, so concurrent sessions clobber each other's
unrelated flags — e.g. `EXPERT` flipping between two logged-on terminals.
That wants a field-level read-modify-write and is not addressed here.
### Known limitation after the fix
With one record and multiple connections the seed remains last-connection-wins; the stored terminal type will track whichever node most recently wrote `misc`. That only matters at the moment someone turns `AUTOTERM` off.
### Test notes
A repro that needs no concurrency: `;chuser` into an account whose record has the `RIP` bit set, from a non-RIP terminal, and check `user.compare_ars("RIP")`. The original report needs a RIP terminal and a non-RIP terminal logged on to the same account simultaneously, a `user.*` property write on the non-RIP node (any settings toggle), and then a message scan.
— *Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.37-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)