Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions docs/DDIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,30 @@ its own upstream hub:
| `CHATTER_DDIAL_HUB_LISTEN` | `port` or `addr:port` (`[v6addr]:port`) to accept station links on. Unset = off. |
| `CHATTER_DDIAL_STATION` | Name shown in the welcome line and the `}}}` user list. |

Operators manage keys from the Chatter room:

- `/ddial key new <station-name>` prints a new nine-digit station key.
**It is shown once.** Give it to that station's operator; it is their
link password (`CHATTER_DDIAL_KEY` on another ssh-chatter).
Operators manage requests and keys from the Chatter room:

- `/ddial requests` lists pending inbound station requests with their ids,
names and source addresses.
- `/ddial approve <request-id>` prints a new nine-digit station key and
removes the request. **The key is shown once.** Give it to that station's
operator; it is their link password (`CHATTER_DDIAL_KEY` on another
ssh-chatter).
- `/ddial deny <request-id>` removes a request without issuing a key.
- `/ddial key new <station-name>` still creates a key directly when no
inbound request is needed.
- `/ddial key list` shows each key's id, name, creation date and last use.
- `/ddial key revoke <id>` deletes a key and drops a station linked with it.
Ids are never reused.
- `/ddial hub` lists the linked stations.

A station without a key sends `@request:<station-name>` at the password
prompt (for example, `CHATTER_DDIAL_KEY='@request:RemoteOne'`). The hub
records the request and closes that login attempt; the station can use the
key after an operator approves it. Repeating a request from the same address
and name keeps its original id. Pending requests are stored in
`ddial_station_requests.dat` next to the member files and rewritten
atomically whenever they change.

Keys are stored as PBKDF2-SHA256 hashes in `ddial_station_keys.tsv` next to
the member files (mode 0600).

Expand Down
120 changes: 120 additions & 0 deletions src/host/core/command_messages.c
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,126 @@ static const session_command_message_t kSessionCommandMessages[] = {
[SESSION_UI_LANGUAGE_PL] = "Wątek wiadomości #%s:",
},
},
{
"Only operators can manage DDial station links.",
{
[SESSION_UI_LANGUAGE_KO] = "DDial 스테이션 링크는 운영자만 관리할 수 있습니다.",
[SESSION_UI_LANGUAGE_JP] = "DDial ステーションリンクを管理できるのはオペレーターのみです。",
[SESSION_UI_LANGUAGE_ZH] = "只有管理员可以管理 DDial 站点链接。",
[SESSION_UI_LANGUAGE_RU] = "Управлять связями станций DDial могут только операторы.",
[SESSION_UI_LANGUAGE_DE] = "Nur Operatoren können DDial-Stationsverbindungen verwalten.",
[SESSION_UI_LANGUAGE_FR] = "Seuls les opérateurs peuvent gérer les liaisons de stations DDial.",
[SESSION_UI_LANGUAGE_PL] = "Tylko operatorzy mogą zarządzać połączeniami stacji DDial.",
},
},
{
"No pending station requests.",
{
[SESSION_UI_LANGUAGE_KO] = "대기 중인 스테이션 요청이 없습니다.",
[SESSION_UI_LANGUAGE_JP] = "保留中のステーション要求はありません。",
[SESSION_UI_LANGUAGE_ZH] = "没有待处理的站点请求。",
[SESSION_UI_LANGUAGE_RU] = "Нет ожидающих запросов станций.",
[SESSION_UI_LANGUAGE_DE] = "Keine ausstehenden Stationsanfragen.",
[SESSION_UI_LANGUAGE_FR] = "Aucune demande de station en attente.",
[SESSION_UI_LANGUAGE_PL] = "Brak oczekujących zgłoszeń stacji.",
},
},
{
"Pending station requests:",
{
[SESSION_UI_LANGUAGE_KO] = "대기 중인 스테이션 요청:",
[SESSION_UI_LANGUAGE_JP] = "保留中のステーション要求:",
[SESSION_UI_LANGUAGE_ZH] = "待处理的站点请求:",
[SESSION_UI_LANGUAGE_RU] = "Ожидающие запросы станций:",
[SESSION_UI_LANGUAGE_DE] = "Ausstehende Stationsanfragen:",
[SESSION_UI_LANGUAGE_FR] = "Demandes de stations en attente :",
[SESSION_UI_LANGUAGE_PL] = "Oczekujące zgłoszenia stacji:",
},
},
{
"Usage: /ddial approve <request-id>",
{
[SESSION_UI_LANGUAGE_KO] = "사용법: /ddial approve <요청ID>",
[SESSION_UI_LANGUAGE_JP] = "使い方: /ddial approve <要求ID>",
[SESSION_UI_LANGUAGE_ZH] = "用法:/ddial approve <请求ID>",
[SESSION_UI_LANGUAGE_RU] = "Использование: /ddial approve <id запроса>",
[SESSION_UI_LANGUAGE_DE] = "Verwendung: /ddial approve <Anfrage-ID>",
[SESSION_UI_LANGUAGE_FR] = "Utilisation : /ddial approve <id-demande>",
[SESSION_UI_LANGUAGE_PL] = "Użycie: /ddial approve <id-zgłoszenia>",
},
},
{
"Usage: /ddial deny <request-id>",
{
[SESSION_UI_LANGUAGE_KO] = "사용법: /ddial deny <요청ID>",
[SESSION_UI_LANGUAGE_JP] = "使い方: /ddial deny <要求ID>",
[SESSION_UI_LANGUAGE_ZH] = "用法:/ddial deny <请求ID>",
[SESSION_UI_LANGUAGE_RU] = "Использование: /ddial deny <id запроса>",
[SESSION_UI_LANGUAGE_DE] = "Verwendung: /ddial deny <Anfrage-ID>",
[SESSION_UI_LANGUAGE_FR] = "Utilisation : /ddial deny <id-demande>",
[SESSION_UI_LANGUAGE_PL] = "Użycie: /ddial deny <id-zgłoszenia>",
},
},
{
"No station request with that id.",
{
[SESSION_UI_LANGUAGE_KO] = "해당 ID의 스테이션 요청이 없습니다.",
[SESSION_UI_LANGUAGE_JP] = "その ID のステーション要求はありません。",
[SESSION_UI_LANGUAGE_ZH] = "没有该 ID 的站点请求。",
[SESSION_UI_LANGUAGE_RU] = "Запрос станции с таким id не найден.",
[SESSION_UI_LANGUAGE_DE] = "Keine Stationsanfrage mit dieser ID.",
[SESSION_UI_LANGUAGE_FR] = "Aucune demande de station avec cet identifiant.",
[SESSION_UI_LANGUAGE_PL] = "Brak zgłoszenia stacji o tym identyfikatorze.",
},
},
{
"Could not approve station request.",
{
[SESSION_UI_LANGUAGE_KO] = "스테이션 요청을 승인하지 못했습니다.",
[SESSION_UI_LANGUAGE_JP] = "ステーション要求を承認できませんでした。",
[SESSION_UI_LANGUAGE_ZH] = "无法批准站点请求。",
[SESSION_UI_LANGUAGE_RU] = "Не удалось одобрить запрос станции.",
[SESSION_UI_LANGUAGE_DE] = "Stationsanfrage konnte nicht genehmigt werden.",
[SESSION_UI_LANGUAGE_FR] = "Impossible d'approuver la demande de station.",
[SESSION_UI_LANGUAGE_PL] = "Nie udało się zatwierdzić zgłoszenia stacji.",
},
},
{
"Could not deny station request.",
{
[SESSION_UI_LANGUAGE_KO] = "스테이션 요청을 거절하지 못했습니다.",
[SESSION_UI_LANGUAGE_JP] = "ステーション要求を拒否できませんでした。",
[SESSION_UI_LANGUAGE_ZH] = "无法拒绝站点请求。",
[SESSION_UI_LANGUAGE_RU] = "Не удалось отклонить запрос станции.",
[SESSION_UI_LANGUAGE_DE] = "Stationsanfrage konnte nicht abgelehnt werden.",
[SESSION_UI_LANGUAGE_FR] = "Impossible de refuser la demande de station.",
[SESSION_UI_LANGUAGE_PL] = "Nie udało się odrzucić zgłoszenia stacji.",
},
},
{
"Station request #%u denied.",
{
[SESSION_UI_LANGUAGE_KO] = "스테이션 요청 #%u을(를) 거절했습니다.",
[SESSION_UI_LANGUAGE_JP] = "ステーション要求 #%u を拒否しました。",
[SESSION_UI_LANGUAGE_ZH] = "已拒绝站点请求 #%u。",
[SESSION_UI_LANGUAGE_RU] = "Запрос станции #%u отклонён.",
[SESSION_UI_LANGUAGE_DE] = "Stationsanfrage #%u abgelehnt.",
[SESSION_UI_LANGUAGE_FR] = "Demande de station #%u refusée.",
[SESSION_UI_LANGUAGE_PL] = "Odrzucono zgłoszenie stacji #%u.",
},
},
{
"Station request #%u approved. Station key #%u for '%s': %s (shown once; give it to that station's operator)",
{
[SESSION_UI_LANGUAGE_KO] = "스테이션 요청 #%u을(를) 승인했습니다. 스테이션 키 #%u ('%s'): %s (한 번만 표시되니 해당 스테이션 운영자에게 전달하세요)",
[SESSION_UI_LANGUAGE_JP] = "ステーション要求 #%u を承認しました。ステーションキー #%u ('%s'): %s (一度だけ表示されます。ステーションのオペレーターに渡してください)",
[SESSION_UI_LANGUAGE_ZH] = "已批准站点请求 #%u。站点密钥 #%u('%s'):%s(仅显示一次,请交给该站点管理员)",
[SESSION_UI_LANGUAGE_RU] = "Запрос станции #%u одобрен. Ключ станции #%u для «%s»: %s (показывается один раз; передайте его оператору станции)",
[SESSION_UI_LANGUAGE_DE] = "Stationsanfrage #%u genehmigt. Stationsschlüssel #%u für „%s“: %s (wird nur einmal angezeigt; an den Stationsoperator weitergeben)",
[SESSION_UI_LANGUAGE_FR] = "Demande de station #%u approuvée. Clé de station #%u pour « %s » : %s (affichée une seule fois ; transmettez-la à l'opérateur de cette station)",
[SESSION_UI_LANGUAGE_PL] = "Zatwierdzono zgłoszenie stacji #%u. Klucz stacji #%u dla „%s”: %s (widoczny tylko raz; przekaż go operatorowi tej stacji)",
},
},
};

// Returns the message in the session's UI language, or the English text when
Expand Down
32 changes: 32 additions & 0 deletions src/host/core/help_entries.c
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,38 @@ static const session_help_entry_t kSessionHelpOperator[] = {
.label_arg_count = 0U,
.label_args = {},
},
{
.kind = SESSION_HELP_ENTRY_COMMAND,
.label = "ddial <requests|approve <request-id>|deny <request-id>>",
.description =
{
"Review inbound station requests. approve issues a key shown "
"once and removes the request; deny removes it without "
"issuing a key (operator only).",
"들어온 스테이션 요청을 검토합니다. approve는 한 번만 표시되는 "
"키를 발급하고 요청을 삭제하며, deny는 키 없이 요청을 삭제합니다 "
"(운영자 전용).",
"受信したステーション要求を確認します。approve は一度だけ表示"
"されるキーを発行して要求を削除し、deny はキーを発行せず要求を"
"削除します(オペレーター専用)。",
"查看收到的站点请求。approve 签发仅显示一次的密钥并删除请求;"
"deny 不签发密钥,直接删除请求(仅限管理员)。",
"Просмотр входящих запросов станций. approve выдаёт ключ, "
"показываемый один раз, и удаляет запрос; deny удаляет запрос "
"без выдачи ключа (только оператор).",
"Eingehende Stationsanfragen prüfen. approve erstellt einen nur "
"einmal angezeigten Schlüssel und löscht die Anfrage; deny "
"löscht sie ohne Schlüssel (nur Operatoren).",
"Examiner les demandes de stations entrantes. approve génère "
"une clé affichée une seule fois et supprime la demande ; deny "
"la supprime sans clé (opérateur uniquement).",
"Przeglądaj przychodzące zgłoszenia stacji. approve wydaje klucz "
"widoczny tylko raz i usuwa zgłoszenie; deny usuwa je bez "
"wydawania klucza (tylko operator).",
},
.label_arg_count = 0U,
.label_args = {},
},
{
.kind = SESSION_HELP_ENTRY_COMMAND,
.label = "resetpw <nickname>",
Expand Down
Loading
Loading