Initial commit
This commit is contained in:
23
.github/workflows/cargo-audit.yml
vendored
Normal file
23
.github/workflows/cargo-audit.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Cargo security audit
|
||||
on:
|
||||
schedule:
|
||||
- cron: "31 1 * * *"
|
||||
|
||||
push:
|
||||
paths:
|
||||
- "**/Cargo.toml"
|
||||
- "**/Cargo.lock"
|
||||
jobs:
|
||||
audit:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./backend
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- uses: rustsec/audit-check@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
32
.github/workflows/cargo-test.yml
vendored
Normal file
32
.github/workflows/cargo-test.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Cargo test & clippy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
|
||||
rust-clippy-analyze:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Run rust-clippy
|
||||
run: cargo clippy --all-features
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.idea
|
||||
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug",
|
||||
"preLaunchTask": "rust: cargo build",
|
||||
"program": "target/debug/telnet_server",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
30
.vscode/tasks.json
vendored
Normal file
30
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "telnet_server: Start TELNET Docker",
|
||||
"type": "shell",
|
||||
"command": "./telnet/run_telnet.sh",
|
||||
"args": [
|
||||
"${input:host}",
|
||||
"${input:port}"
|
||||
],
|
||||
"detail": "Starts a Docker container (in interactive mode) that runs TELNET and connects to given host and port",
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "host",
|
||||
"type": "promptString",
|
||||
"default": "host.docker.internal",
|
||||
"description": "Which host should the TELNET client connect to?"
|
||||
},
|
||||
{
|
||||
"id": "port",
|
||||
"type": "promptString",
|
||||
"default": "9000",
|
||||
"description": "Which port should the TELNET client connect to?"
|
||||
},
|
||||
]
|
||||
}
|
||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "telnet_server"
|
||||
version = "0.0.1"
|
||||
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "telnet_server"
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
authors = ["Laika Schmidt <laika.schmidt@magenta.de>"]
|
||||
description = "A bare TELNET server provider"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/its-laika/telnet_server"
|
||||
license = "MIT"
|
||||
keywords = ["TELNET"]
|
||||
exclude = [".github/", ".vscode/", "resources/", "telnet/", ".gitignore"]
|
||||
|
||||
[lib]
|
||||
name = "telnet_server"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "telnet_server"
|
||||
path = "src/bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 - 2025 Laika Schmidt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
32
README.md
Normal file
32
README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# telnet_server
|
||||
_...because nothing beats the charm of the seventies!_
|
||||
|
||||
## Goals
|
||||
This lib should provide everything to build a service (based on TELNET) without
|
||||
having to touch any TELNET or tcp specifics. Message communication should only
|
||||
happen between this lib and the code providing the service.
|
||||
|
||||
See [src/bin/main.rs](src/bin/main.rs#l23) on how I think this should work.
|
||||
|
||||
## Status
|
||||
Non functional and heavily WIP. **DO NOT USE!**
|
||||
|
||||
## Running TELNET
|
||||
As TELNET is not anymore part of modern operating systems (thank god), I've
|
||||
created a minimal Dockerfile that let's me use TELNET on CLI. TELNET starts via
|
||||
this command:
|
||||
|
||||
```sh
|
||||
./telnet/run_telnet.sh HOST PORT
|
||||
```
|
||||
|
||||
For development, _HOST_ is "host.docker.internal" and _PORT_ is 9000.
|
||||
|
||||
## For f*cks sake, why TELNET???
|
||||
Because it looked interesting. Honestly, even if I get it finished, the library
|
||||
likely won't be used _at all_.
|
||||
|
||||
## License
|
||||
The code is licensed under the [MIT License](LICENSE). The RFCs in the
|
||||
[resources](resources) folder have a different copyright but are allowed (and
|
||||
encouraged) to be copied and redistributed if unchanged.
|
||||
36
resources/ascii.txt
Normal file
36
resources/ascii.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
Source: https://www.cs.cmu.edu/~pattis/15-1XX/common/handouts/ascii.html (downloaded: 2024-03-03 12:24 UTC)
|
||||
|
||||
Dec Char Dec Char Dec Char Dec Char
|
||||
--------- --------- --------- ----------
|
||||
0 NUL (null) 32 SPACE 64 @ 96 `
|
||||
1 SOH (start of heading) 33 ! 65 A 97 a
|
||||
2 STX (start of text) 34 " 66 B 98 b
|
||||
3 ETX (end of text) 35 # 67 C 99 c
|
||||
4 EOT (end of transmission) 36 $ 68 D 100 d
|
||||
5 ENQ (enquiry) 37 % 69 E 101 e
|
||||
6 ACK (acknowledge) 38 & 70 F 102 f
|
||||
7 BEL (bell) 39 ' 71 G 103 g
|
||||
8 BS (backspace) 40 ( 72 H 104 h
|
||||
9 TAB (horizontal tab) 41 ) 73 I 105 i
|
||||
10 LF (NL line feed, new line) 42 * 74 J 106 j
|
||||
11 VT (vertical tab) 43 + 75 K 107 k
|
||||
12 FF (NP form feed, new page) 44 , 76 L 108 l
|
||||
13 CR (carriage return) 45 - 77 M 109 m
|
||||
14 SO (shift out) 46 . 78 N 110 n
|
||||
15 SI (shift in) 47 / 79 O 111 o
|
||||
16 DLE (data link escape) 48 0 80 P 112 p
|
||||
17 DC1 (device control 1) 49 1 81 Q 113 q
|
||||
18 DC2 (device control 2) 50 2 82 R 114 r
|
||||
19 DC3 (device control 3) 51 3 83 S 115 s
|
||||
20 DC4 (device control 4) 52 4 84 T 116 t
|
||||
21 NAK (negative acknowledge) 53 5 85 U 117 u
|
||||
22 SYN (synchronous idle) 54 6 86 V 118 v
|
||||
23 ETB (end of trans. block) 55 7 87 W 119 w
|
||||
24 CAN (cancel) 56 8 88 X 120 x
|
||||
25 EM (end of medium) 57 9 89 Y 121 y
|
||||
26 SUB (substitute) 58 : 90 Z 122 z
|
||||
27 ESC (escape) 59 ; 91 [ 123 {
|
||||
28 FS (file separator) 60 < 92 \ 124 |
|
||||
29 GS (group separator) 61 = 93 ] 125 }
|
||||
30 RS (record separator) 62 > 94 ^ 126 ~
|
||||
31 US (unit separator) 63 ? 95 _ 127 DEL
|
||||
1287
resources/rfc1184.txt
Normal file
1287
resources/rfc1184.txt
Normal file
File diff suppressed because it is too large
Load Diff
855
resources/rfc854.txt
Normal file
855
resources/rfc854.txt
Normal file
@@ -0,0 +1,855 @@
|
||||
Source: https://datatracker.ietf.org/doc/html/rfc854 (downloaded: 2024-03-03 12:20 UTC)
|
||||
|
||||
Network Working Group J. Postel
|
||||
Request for Comments: 854 J. Reynolds
|
||||
ISI
|
||||
Obsoletes: NIC 18639 May 1983
|
||||
|
||||
TELNET PROTOCOL SPECIFICATION
|
||||
|
||||
|
||||
This RFC specifies a standard for the ARPA Internet community. Hosts on
|
||||
the ARPA Internet are expected to adopt and implement this standard.
|
||||
|
||||
INTRODUCTION
|
||||
|
||||
The purpose of the TELNET Protocol is to provide a fairly general,
|
||||
bi-directional, eight-bit byte oriented communications facility. Its
|
||||
primary goal is to allow a standard method of interfacing terminal
|
||||
devices and terminal-oriented processes to each other. It is
|
||||
envisioned that the protocol may also be used for terminal-terminal
|
||||
communication ("linking") and process-process communication
|
||||
(distributed computation).
|
||||
|
||||
GENERAL CONSIDERATIONS
|
||||
|
||||
A TELNET connection is a Transmission Control Protocol (TCP)
|
||||
connection used to transmit data with interspersed TELNET control
|
||||
information.
|
||||
|
||||
The TELNET Protocol is built upon three main ideas: first, the
|
||||
concept of a "Network Virtual Terminal"; second, the principle of
|
||||
negotiated options; and third, a symmetric view of terminals and
|
||||
processes.
|
||||
|
||||
1. When a TELNET connection is first established, each end is
|
||||
assumed to originate and terminate at a "Network Virtual Terminal",
|
||||
or NVT. An NVT is an imaginary device which provides a standard,
|
||||
network-wide, intermediate representation of a canonical terminal.
|
||||
This eliminates the need for "server" and "user" hosts to keep
|
||||
information about the characteristics of each other's terminals and
|
||||
terminal handling conventions. All hosts, both user and server, map
|
||||
their local device characteristics and conventions so as to appear to
|
||||
be dealing with an NVT over the network, and each can assume a
|
||||
similar mapping by the other party. The NVT is intended to strike a
|
||||
balance between being overly restricted (not providing hosts a rich
|
||||
enough vocabulary for mapping into their local character sets), and
|
||||
being overly inclusive (penalizing users with modest terminals).
|
||||
|
||||
NOTE: The "user" host is the host to which the physical terminal
|
||||
is normally attached, and the "server" host is the host which is
|
||||
normally providing some service. As an alternate point of view,
|
||||
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 1]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
applicable even in terminal-to-terminal or process-to-process
|
||||
communications, the "user" host is the host which initiated the
|
||||
communication.
|
||||
|
||||
2. The principle of negotiated options takes cognizance of the fact
|
||||
that many hosts will wish to provide additional services over and
|
||||
above those available within an NVT, and many users will have
|
||||
sophisticated terminals and would like to have elegant, rather than
|
||||
minimal, services. Independent of, but structured within the TELNET
|
||||
Protocol are various "options" that will be sanctioned and may be
|
||||
used with the "DO, DON'T, WILL, WON'T" structure (discussed below) to
|
||||
allow a user and server to agree to use a more elaborate (or perhaps
|
||||
just different) set of conventions for their TELNET connection. Such
|
||||
options could include changing the character set, the echo mode, etc.
|
||||
|
||||
The basic strategy for setting up the use of options is to have
|
||||
either party (or both) initiate a request that some option take
|
||||
effect. The other party may then either accept or reject the
|
||||
request. If the request is accepted the option immediately takes
|
||||
effect; if it is rejected the associated aspect of the connection
|
||||
remains as specified for an NVT. Clearly, a party may always refuse
|
||||
a request to enable, and must never refuse a request to disable some
|
||||
option since all parties must be prepared to support the NVT.
|
||||
|
||||
The syntax of option negotiation has been set up so that if both
|
||||
parties request an option simultaneously, each will see the other's
|
||||
request as the positive acknowledgment of its own.
|
||||
|
||||
3. The symmetry of the negotiation syntax can potentially lead to
|
||||
nonterminating acknowledgment loops -- each party seeing the incoming
|
||||
commands not as acknowledgments but as new requests which must be
|
||||
acknowledged. To prevent such loops, the following rules prevail:
|
||||
|
||||
a. Parties may only request a change in option status; i.e., a
|
||||
party may not send out a "request" merely to announce what mode it
|
||||
is in.
|
||||
|
||||
b. If a party receives what appears to be a request to enter some
|
||||
mode it is already in, the request should not be acknowledged.
|
||||
This non-response is essential to prevent endless loops in the
|
||||
negotiation. It is required that a response be sent to requests
|
||||
for a change of mode -- even if the mode is not changed.
|
||||
|
||||
c. Whenever one party sends an option command to a second party,
|
||||
whether as a request or an acknowledgment, and use of the option
|
||||
will have any effect on the processing of the data being sent from
|
||||
the first party to the second, then the command must be inserted
|
||||
in the data stream at the point where it is desired that it take
|
||||
|
||||
|
||||
Postel & Reynolds [Page 2]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
effect. (It should be noted that some time will elapse between
|
||||
the transmission of a request and the receipt of an
|
||||
acknowledgment, which may be negative. Thus, a host may wish to
|
||||
buffer data, after requesting an option, until it learns whether
|
||||
the request is accepted or rejected, in order to hide the
|
||||
"uncertainty period" from the user.)
|
||||
|
||||
Option requests are likely to flurry back and forth when a TELNET
|
||||
connection is first established, as each party attempts to get the
|
||||
best possible service from the other party. Beyond that, however,
|
||||
options can be used to dynamically modify the characteristics of the
|
||||
connection to suit changing local conditions. For example, the NVT,
|
||||
as will be explained later, uses a transmission discipline well
|
||||
suited to the many "line at a time" applications such as BASIC, but
|
||||
poorly suited to the many "character at a time" applications such as
|
||||
NLS. A server might elect to devote the extra processor overhead
|
||||
required for a "character at a time" discipline when it was suitable
|
||||
for the local process and would negotiate an appropriate option.
|
||||
However, rather than then being permanently burdened with the extra
|
||||
processing overhead, it could switch (i.e., negotiate) back to NVT
|
||||
when the detailed control was no longer necessary.
|
||||
|
||||
It is possible for requests initiated by processes to stimulate a
|
||||
nonterminating request loop if the process responds to a rejection by
|
||||
merely re-requesting the option. To prevent such loops from
|
||||
occurring, rejected requests should not be repeated until something
|
||||
changes. Operationally, this can mean the process is running a
|
||||
different program, or the user has given another command, or whatever
|
||||
makes sense in the context of the given process and the given option.
|
||||
A good rule of thumb is that a re-request should only occur as a
|
||||
result of subsequent information from the other end of the connection
|
||||
or when demanded by local human intervention.
|
||||
|
||||
Option designers should not feel constrained by the somewhat limited
|
||||
syntax available for option negotiation. The intent of the simple
|
||||
syntax is to make it easy to have options -- since it is
|
||||
correspondingly easy to profess ignorance about them. If some
|
||||
particular option requires a richer negotiation structure than
|
||||
possible within "DO, DON'T, WILL, WON'T", the proper tack is to use
|
||||
"DO, DON'T, WILL, WON'T" to establish that both parties understand
|
||||
the option, and once this is accomplished a more exotic syntax can be
|
||||
used freely. For example, a party might send a request to alter
|
||||
(establish) line length. If it is accepted, then a different syntax
|
||||
can be used for actually negotiating the line length -- such a
|
||||
"sub-negotiation" might include fields for minimum allowable, maximum
|
||||
allowable and desired line lengths. The important concept is that
|
||||
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 3]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
such expanded negotiations should never begin until some prior
|
||||
(standard) negotiation has established that both parties are capable
|
||||
of parsing the expanded syntax.
|
||||
|
||||
In summary, WILL XXX is sent, by either party, to indicate that
|
||||
party's desire (offer) to begin performing option XXX, DO XXX and
|
||||
DON'T XXX being its positive and negative acknowledgments; similarly,
|
||||
DO XXX is sent to indicate a desire (request) that the other party
|
||||
(i.e., the recipient of the DO) begin performing option XXX, WILL XXX
|
||||
and WON'T XXX being the positive and negative acknowledgments. Since
|
||||
the NVT is what is left when no options are enabled, the DON'T and
|
||||
WON'T responses are guaranteed to leave the connection in a state
|
||||
which both ends can handle. Thus, all hosts may implement their
|
||||
TELNET processes to be totally unaware of options that are not
|
||||
supported, simply returning a rejection to (i.e., refusing) any
|
||||
option request that cannot be understood.
|
||||
|
||||
As much as possible, the TELNET protocol has been made server-user
|
||||
symmetrical so that it easily and naturally covers the user-user
|
||||
(linking) and server-server (cooperating processes) cases. It is
|
||||
hoped, but not absolutely required, that options will further this
|
||||
intent. In any case, it is explicitly acknowledged that symmetry is
|
||||
an operating principle rather than an ironclad rule.
|
||||
|
||||
A companion document, "TELNET Option Specifications," should be
|
||||
consulted for information about the procedure for establishing new
|
||||
options.
|
||||
|
||||
THE NETWORK VIRTUAL TERMINAL
|
||||
|
||||
The Network Virtual Terminal (NVT) is a bi-directional character
|
||||
device. The NVT has a printer and a keyboard. The printer responds
|
||||
to incoming data and the keyboard produces outgoing data which is
|
||||
sent over the TELNET connection and, if "echoes" are desired, to the
|
||||
NVT's printer as well. "Echoes" will not be expected to traverse the
|
||||
network (although options exist to enable a "remote" echoing mode of
|
||||
operation, no host is required to implement this option). The code
|
||||
set is seven-bit USASCII in an eight-bit field, except as modified
|
||||
herein. Any code conversion and timing considerations are local
|
||||
problems and do not affect the NVT.
|
||||
|
||||
TRANSMISSION OF DATA
|
||||
|
||||
Although a TELNET connection through the network is intrinsically
|
||||
full duplex, the NVT is to be viewed as a half-duplex device
|
||||
operating in a line-buffered mode. That is, unless and until
|
||||
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 4]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
options are negotiated to the contrary, the following default
|
||||
conditions pertain to the transmission of data over the TELNET
|
||||
connection:
|
||||
|
||||
1) Insofar as the availability of local buffer space permits,
|
||||
data should be accumulated in the host where it is generated
|
||||
until a complete line of data is ready for transmission, or
|
||||
until some locally-defined explicit signal to transmit occurs.
|
||||
This signal could be generated either by a process or by a
|
||||
human user.
|
||||
|
||||
The motivation for this rule is the high cost, to some hosts,
|
||||
of processing network input interrupts, coupled with the
|
||||
default NVT specification that "echoes" do not traverse the
|
||||
network. Thus, it is reasonable to buffer some amount of data
|
||||
at its source. Many systems take some processing action at the
|
||||
end of each input line (even line printers or card punches
|
||||
frequently tend to work this way), so the transmission should
|
||||
be triggered at the end of a line. On the other hand, a user
|
||||
or process may sometimes find it necessary or desirable to
|
||||
provide data which does not terminate at the end of a line;
|
||||
therefore implementers are cautioned to provide methods of
|
||||
locally signaling that all buffered data should be transmitted
|
||||
immediately.
|
||||
|
||||
2) When a process has completed sending data to an NVT printer
|
||||
and has no queued input from the NVT keyboard for further
|
||||
processing (i.e., when a process at one end of a TELNET
|
||||
connection cannot proceed without input from the other end),
|
||||
the process must transmit the TELNET Go Ahead (GA) command.
|
||||
|
||||
This rule is not intended to require that the TELNET GA command
|
||||
be sent from a terminal at the end of each line, since server
|
||||
hosts do not normally require a special signal (in addition to
|
||||
end-of-line or other locally-defined characters) in order to
|
||||
commence processing. Rather, the TELNET GA is designed to help
|
||||
a user's local host operate a physically half duplex terminal
|
||||
which has a "lockable" keyboard such as the IBM 2741. A
|
||||
description of this type of terminal may help to explain the
|
||||
proper use of the GA command.
|
||||
|
||||
The terminal-computer connection is always under control of
|
||||
either the user or the computer. Neither can unilaterally
|
||||
seize control from the other; rather the controlling end must
|
||||
relinguish its control explicitly. At the terminal end, the
|
||||
hardware is constructed so as to relinquish control each time
|
||||
that a "line" is terminated (i.e., when the "New Line" key is
|
||||
typed by the user). When this occurs, the attached (local)
|
||||
|
||||
|
||||
Postel & Reynolds [Page 5]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
computer processes the input data, decides if output should be
|
||||
generated, and if not returns control to the terminal. If
|
||||
output should be generated, control is retained by the computer
|
||||
until all output has been transmitted.
|
||||
|
||||
The difficulties of using this type of terminal through the
|
||||
network should be obvious. The "local" computer is no longer
|
||||
able to decide whether to retain control after seeing an
|
||||
end-of-line signal or not; this decision can only be made by
|
||||
the "remote" computer which is processing the data. Therefore,
|
||||
the TELNET GA command provides a mechanism whereby the "remote"
|
||||
(server) computer can signal the "local" (user) computer that
|
||||
it is time to pass control to the user of the terminal. It
|
||||
should be transmitted at those times, and only at those times,
|
||||
when the user should be given control of the terminal. Note
|
||||
that premature transmission of the GA command may result in the
|
||||
blocking of output, since the user is likely to assume that the
|
||||
transmitting system has paused, and therefore he will fail to
|
||||
turn the line around manually.
|
||||
|
||||
The foregoing, of course, does not apply to the user-to-server
|
||||
direction of communication. In this direction, GAs may be sent at
|
||||
any time, but need not ever be sent. Also, if the TELNET
|
||||
connection is being used for process-to-process communication, GAs
|
||||
need not be sent in either direction. Finally, for
|
||||
terminal-to-terminal communication, GAs may be required in
|
||||
neither, one, or both directions. If a host plans to support
|
||||
terminal-to-terminal communication it is suggested that the host
|
||||
provide the user with a means of manually signaling that it is
|
||||
time for a GA to be sent over the TELNET connection; this,
|
||||
however, is not a requirement on the implementer of a TELNET
|
||||
process.
|
||||
|
||||
Note that the symmetry of the TELNET model requires that there is
|
||||
an NVT at each end of the TELNET connection, at least
|
||||
conceptually.
|
||||
|
||||
STANDARD REPRESENTATION OF CONTROL FUNCTIONS
|
||||
|
||||
As stated in the Introduction to this document, the primary goal
|
||||
of the TELNET protocol is the provision of a standard interfacing
|
||||
of terminal devices and terminal-oriented processes through the
|
||||
network. Early experiences with this type of interconnection have
|
||||
shown that certain functions are implemented by most servers, but
|
||||
that the methods of invoking these functions differ widely. For a
|
||||
human user who interacts with several server systems, these
|
||||
differences are highly frustrating. TELNET, therefore, defines a
|
||||
standard representation for five of these functions, as described
|
||||
|
||||
|
||||
Postel & Reynolds [Page 6]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
below. These standard representations have standard, but not
|
||||
required, meanings (with the exception that the Interrupt Process
|
||||
(IP) function may be required by other protocols which use
|
||||
TELNET); that is, a system which does not provide the function to
|
||||
local users need not provide it to network users and may treat the
|
||||
standard representation for the function as a No-operation. On
|
||||
the other hand, a system which does provide the function to a
|
||||
local user is obliged to provide the same function to a network
|
||||
user who transmits the standard representation for the function.
|
||||
|
||||
Interrupt Process (IP)
|
||||
|
||||
Many systems provide a function which suspends, interrupts,
|
||||
aborts, or terminates the operation of a user process. This
|
||||
function is frequently used when a user believes his process is
|
||||
in an unending loop, or when an unwanted process has been
|
||||
inadvertently activated. IP is the standard representation for
|
||||
invoking this function. It should be noted by implementers
|
||||
that IP may be required by other protocols which use TELNET,
|
||||
and therefore should be implemented if these other protocols
|
||||
are to be supported.
|
||||
|
||||
Abort Output (AO)
|
||||
|
||||
Many systems provide a function which allows a process, which
|
||||
is generating output, to run to completion (or to reach the
|
||||
same stopping point it would reach if running to completion)
|
||||
but without sending the output to the user's terminal.
|
||||
Further, this function typically clears any output already
|
||||
produced but not yet actually printed (or displayed) on the
|
||||
user's terminal. AO is the standard representation for
|
||||
invoking this function. For example, some subsystem might
|
||||
normally accept a user's command, send a long text string to
|
||||
the user's terminal in response, and finally signal readiness
|
||||
to accept the next command by sending a "prompt" character
|
||||
(preceded by <CR><LF>) to the user's terminal. If the AO were
|
||||
received during the transmission of the text string, a
|
||||
reasonable implementation would be to suppress the remainder of
|
||||
the text string, but transmit the prompt character and the
|
||||
preceding <CR><LF>. (This is possibly in distinction to the
|
||||
action which might be taken if an IP were received; the IP
|
||||
might cause suppression of the text string and an exit from the
|
||||
subsystem.)
|
||||
|
||||
It should be noted, by server systems which provide this
|
||||
function, that there may be buffers external to the system (in
|
||||
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 7]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
the network and the user's local host) which should be cleared;
|
||||
the appropriate way to do this is to transmit the "Synch"
|
||||
signal (described below) to the user system.
|
||||
|
||||
Are You There (AYT)
|
||||
|
||||
Many systems provide a function which provides the user with
|
||||
some visible (e.g., printable) evidence that the system is
|
||||
still up and running. This function may be invoked by the user
|
||||
when the system is unexpectedly "silent" for a long time,
|
||||
because of the unanticipated (by the user) length of a
|
||||
computation, an unusually heavy system load, etc. AYT is the
|
||||
standard representation for invoking this function.
|
||||
|
||||
Erase Character (EC)
|
||||
|
||||
Many systems provide a function which deletes the last
|
||||
preceding undeleted character or "print position"* from the
|
||||
stream of data being supplied by the user. This function is
|
||||
typically used to edit keyboard input when typing mistakes are
|
||||
made. EC is the standard representation for invoking this
|
||||
function.
|
||||
|
||||
*NOTE: A "print position" may contain several characters
|
||||
which are the result of overstrikes, or of sequences such as
|
||||
<char1> BS <char2>...
|
||||
|
||||
Erase Line (EL)
|
||||
|
||||
Many systems provide a function which deletes all the data in
|
||||
the current "line" of input. This function is typically used
|
||||
to edit keyboard input. EL is the standard representation for
|
||||
invoking this function.
|
||||
|
||||
THE TELNET "SYNCH" SIGNAL
|
||||
|
||||
Most time-sharing systems provide mechanisms which allow a
|
||||
terminal user to regain control of a "runaway" process; the IP and
|
||||
AO functions described above are examples of these mechanisms.
|
||||
Such systems, when used locally, have access to all of the signals
|
||||
supplied by the user, whether these are normal characters or
|
||||
special "out of band" signals such as those supplied by the
|
||||
teletype "BREAK" key or the IBM 2741 "ATTN" key. This is not
|
||||
necessarily true when terminals are connected to the system
|
||||
through the network; the network's flow control mechanisms may
|
||||
cause such a signal to be buffered elsewhere, for example in the
|
||||
user's host.
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 8]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
To counter this problem, the TELNET "Synch" mechanism is
|
||||
introduced. A Synch signal consists of a TCP Urgent notification,
|
||||
coupled with the TELNET command DATA MARK. The Urgent
|
||||
notification, which is not subject to the flow control pertaining
|
||||
to the TELNET connection, is used to invoke special handling of
|
||||
the data stream by the process which receives it. In this mode,
|
||||
the data stream is immediately scanned for "interesting" signals
|
||||
as defined below, discarding intervening data. The TELNET command
|
||||
DATA MARK (DM) is the synchronizing mark in the data stream which
|
||||
indicates that any special signal has already occurred and the
|
||||
recipient can return to normal processing of the data stream.
|
||||
|
||||
The Synch is sent via the TCP send operation with the Urgent
|
||||
flag set and the DM as the last (or only) data octet.
|
||||
|
||||
When several Synchs are sent in rapid succession, the Urgent
|
||||
notifications may be merged. It is not possible to count Urgents
|
||||
since the number received will be less than or equal the number
|
||||
sent. When in normal mode, a DM is a no operation; when in urgent
|
||||
mode, it signals the end of the urgent processing.
|
||||
|
||||
If TCP indicates the end of Urgent data before the DM is found,
|
||||
TELNET should continue the special handling of the data stream
|
||||
until the DM is found.
|
||||
|
||||
If TCP indicates more Urgent data after the DM is found, it can
|
||||
only be because of a subsequent Synch. TELNET should continue
|
||||
the special handling of the data stream until another DM is
|
||||
found.
|
||||
|
||||
"Interesting" signals are defined to be: the TELNET standard
|
||||
representations of IP, AO, and AYT (but not EC or EL); the local
|
||||
analogs of these standard representations (if any); all other
|
||||
TELNET commands; other site-defined signals which can be acted on
|
||||
without delaying the scan of the data stream.
|
||||
|
||||
Since one effect of the SYNCH mechanism is the discarding of
|
||||
essentially all characters (except TELNET commands) between the
|
||||
sender of the Synch and its recipient, this mechanism is specified
|
||||
as the standard way to clear the data path when that is desired.
|
||||
For example, if a user at a terminal causes an AO to be
|
||||
transmitted, the server which receives the AO (if it provides that
|
||||
function at all) should return a Synch to the user.
|
||||
|
||||
Finally, just as the TCP Urgent notification is needed at the
|
||||
TELNET level as an out-of-band signal, so other protocols which
|
||||
make use of TELNET may require a TELNET command which can be
|
||||
viewed as an out-of-band signal at a different level.
|
||||
|
||||
|
||||
Postel & Reynolds [Page 9]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
By convention the sequence [IP, Synch] is to be used as such a
|
||||
signal. For example, suppose that some other protocol, which uses
|
||||
TELNET, defines the character string STOP analogously to the
|
||||
TELNET command AO. Imagine that a user of this protocol wishes a
|
||||
server to process the STOP string, but the connection is blocked
|
||||
because the server is processing other commands. The user should
|
||||
instruct his system to:
|
||||
|
||||
1. Send the TELNET IP character;
|
||||
|
||||
2. Send the TELNET SYNC sequence, that is:
|
||||
|
||||
Send the Data Mark (DM) as the only character
|
||||
in a TCP urgent mode send operation.
|
||||
|
||||
3. Send the character string STOP; and
|
||||
|
||||
4. Send the other protocol's analog of the TELNET DM, if any.
|
||||
|
||||
The user (or process acting on his behalf) must transmit the
|
||||
TELNET SYNCH sequence of step 2 above to ensure that the TELNET IP
|
||||
gets through to the server's TELNET interpreter.
|
||||
|
||||
The Urgent should wake up the TELNET process; the IP should
|
||||
wake up the next higher level process.
|
||||
|
||||
THE NVT PRINTER AND KEYBOARD
|
||||
|
||||
The NVT printer has an unspecified carriage width and page length
|
||||
and can produce representations of all 95 USASCII graphics (codes
|
||||
32 through 126). Of the 33 USASCII control codes (0 through 31
|
||||
and 127), and the 128 uncovered codes (128 through 255), the
|
||||
following have specified meaning to the NVT printer:
|
||||
|
||||
NAME CODE MEANING
|
||||
|
||||
NULL (NUL) 0 No Operation
|
||||
Line Feed (LF) 10 Moves the printer to the
|
||||
next print line, keeping the
|
||||
same horizontal position.
|
||||
Carriage Return (CR) 13 Moves the printer to the left
|
||||
margin of the current line.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 10]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
In addition, the following codes shall have defined, but not
|
||||
required, effects on the NVT printer. Neither end of a TELNET
|
||||
connection may assume that the other party will take, or will
|
||||
have taken, any particular action upon receipt or transmission
|
||||
of these:
|
||||
|
||||
BELL (BEL) 7 Produces an audible or
|
||||
visible signal (which does
|
||||
NOT move the print head).
|
||||
Back Space (BS) 8 Moves the print head one
|
||||
character position towards
|
||||
the left margin.
|
||||
Horizontal Tab (HT) 9 Moves the printer to the
|
||||
next horizontal tab stop.
|
||||
It remains unspecified how
|
||||
either party determines or
|
||||
establishes where such tab
|
||||
stops are located.
|
||||
Vertical Tab (VT) 11 Moves the printer to the
|
||||
next vertical tab stop. It
|
||||
remains unspecified how
|
||||
either party determines or
|
||||
establishes where such tab
|
||||
stops are located.
|
||||
Form Feed (FF) 12 Moves the printer to the top
|
||||
of the next page, keeping
|
||||
the same horizontal position.
|
||||
|
||||
All remaining codes do not cause the NVT printer to take any
|
||||
action.
|
||||
|
||||
The sequence "CR LF", as defined, will cause the NVT to be
|
||||
positioned at the left margin of the next print line (as would,
|
||||
for example, the sequence "LF CR"). However, many systems and
|
||||
terminals do not treat CR and LF independently, and will have to
|
||||
go to some effort to simulate their effect. (For example, some
|
||||
terminals do not have a CR independent of the LF, but on such
|
||||
terminals it may be possible to simulate a CR by backspacing.)
|
||||
Therefore, the sequence "CR LF" must be treated as a single "new
|
||||
line" character and used whenever their combined action is
|
||||
intended; the sequence "CR NUL" must be used where a carriage
|
||||
return alone is actually desired; and the CR character must be
|
||||
avoided in other contexts. This rule gives assurance to systems
|
||||
which must decide whether to perform a "new line" function or a
|
||||
multiple-backspace that the TELNET stream contains a character
|
||||
following a CR that will allow a rational decision.
|
||||
|
||||
Note that "CR LF" or "CR NUL" is required in both directions
|
||||
|
||||
|
||||
Postel & Reynolds [Page 11]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
(in the default ASCII mode), to preserve the symmetry of the
|
||||
NVT model. Even though it may be known in some situations
|
||||
(e.g., with remote echo and suppress go ahead options in
|
||||
effect) that characters are not being sent to an actual
|
||||
printer, nonetheless, for the sake of consistency, the protocol
|
||||
requires that a NUL be inserted following a CR not followed by
|
||||
a LF in the data stream. The converse of this is that a NUL
|
||||
received in the data stream after a CR (in the absence of
|
||||
options negotiations which explicitly specify otherwise) should
|
||||
be stripped out prior to applying the NVT to local character
|
||||
set mapping.
|
||||
|
||||
The NVT keyboard has keys, or key combinations, or key sequences,
|
||||
for generating all 128 USASCII codes. Note that although many
|
||||
have no effect on the NVT printer, the NVT keyboard is capable of
|
||||
generating them.
|
||||
|
||||
In addition to these codes, the NVT keyboard shall be capable of
|
||||
generating the following additional codes which, except as noted,
|
||||
have defined, but not reguired, meanings. The actual code
|
||||
assignments for these "characters" are in the TELNET Command
|
||||
section, because they are viewed as being, in some sense, generic
|
||||
and should be available even when the data stream is interpreted
|
||||
as being some other character set.
|
||||
|
||||
Synch
|
||||
|
||||
This key allows the user to clear his data path to the other
|
||||
party. The activation of this key causes a DM (see command
|
||||
section) to be sent in the data stream and a TCP Urgent
|
||||
notification is associated with it. The pair DM-Urgent is to
|
||||
have required meaning as defined previously.
|
||||
|
||||
Break (BRK)
|
||||
|
||||
This code is provided because it is a signal outside the
|
||||
USASCII set which is currently given local meaning within many
|
||||
systems. It is intended to indicate that the Break Key or the
|
||||
Attention Key was hit. Note, however, that this is intended to
|
||||
provide a 129th code for systems which require it, not as a
|
||||
synonym for the IP standard representation.
|
||||
|
||||
Interrupt Process (IP)
|
||||
|
||||
Suspend, interrupt, abort or terminate the process to which the
|
||||
NVT is connected. Also, part of the out-of-band signal for
|
||||
other protocols which use TELNET.
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 12]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
Abort Output (AO)
|
||||
|
||||
Allow the current process to (appear to) run to completion, but
|
||||
do not send its output to the user. Also, send a Synch to the
|
||||
user.
|
||||
|
||||
Are You There (AYT)
|
||||
|
||||
Send back to the NVT some visible (i.e., printable) evidence
|
||||
that the AYT was received.
|
||||
|
||||
Erase Character (EC)
|
||||
|
||||
The recipient should delete the last preceding undeleted
|
||||
character or "print position" from the data stream.
|
||||
|
||||
Erase Line (EL)
|
||||
|
||||
The recipient should delete characters from the data stream
|
||||
back to, but not including, the last "CR LF" sequence sent over
|
||||
the TELNET connection.
|
||||
|
||||
The spirit of these "extra" keys, and also the printer format
|
||||
effectors, is that they should represent a natural extension of
|
||||
the mapping that already must be done from "NVT" into "local".
|
||||
Just as the NVT data byte 68 (104 octal) should be mapped into
|
||||
whatever the local code for "uppercase D" is, so the EC character
|
||||
should be mapped into whatever the local "Erase Character"
|
||||
function is. Further, just as the mapping for 124 (174 octal) is
|
||||
somewhat arbitrary in an environment that has no "vertical bar"
|
||||
character, the EL character may have a somewhat arbitrary mapping
|
||||
(or none at all) if there is no local "Erase Line" facility.
|
||||
Similarly for format effectors: if the terminal actually does
|
||||
have a "Vertical Tab", then the mapping for VT is obvious, and
|
||||
only when the terminal does not have a vertical tab should the
|
||||
effect of VT be unpredictable.
|
||||
|
||||
TELNET COMMAND STRUCTURE
|
||||
|
||||
All TELNET commands consist of at least a two byte sequence: the
|
||||
"Interpret as Command" (IAC) escape character followed by the code
|
||||
for the command. The commands dealing with option negotiation are
|
||||
three byte sequences, the third byte being the code for the option
|
||||
referenced. This format was chosen so that as more comprehensive use
|
||||
of the "data space" is made -- by negotiations from the basic NVT, of
|
||||
course -- collisions of data bytes with reserved command values will
|
||||
be minimized, all such collisions requiring the inconvenience, and
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 13]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
inefficiency, of "escaping" the data bytes into the stream. With the
|
||||
current set-up, only the IAC need be doubled to be sent as data, and
|
||||
the other 255 codes may be passed transparently.
|
||||
|
||||
The following are the defined TELNET commands. Note that these codes
|
||||
and code sequences have the indicated meaning only when immediately
|
||||
preceded by an IAC.
|
||||
|
||||
NAME CODE MEANING
|
||||
|
||||
SE 240 End of subnegotiation parameters.
|
||||
NOP 241 No operation.
|
||||
Data Mark 242 The data stream portion of a Synch.
|
||||
This should always be accompanied
|
||||
by a TCP Urgent notification.
|
||||
Break 243 NVT character BRK.
|
||||
Interrupt Process 244 The function IP.
|
||||
Abort output 245 The function AO.
|
||||
Are You There 246 The function AYT.
|
||||
Erase character 247 The function EC.
|
||||
Erase Line 248 The function EL.
|
||||
Go ahead 249 The GA signal.
|
||||
SB 250 Indicates that what follows is
|
||||
subnegotiation of the indicated
|
||||
option.
|
||||
WILL (option code) 251 Indicates the desire to begin
|
||||
performing, or confirmation that
|
||||
you are now performing, the
|
||||
indicated option.
|
||||
WON'T (option code) 252 Indicates the refusal to perform,
|
||||
or continue performing, the
|
||||
indicated option.
|
||||
DO (option code) 253 Indicates the request that the
|
||||
other party perform, or
|
||||
confirmation that you are expecting
|
||||
the other party to perform, the
|
||||
indicated option.
|
||||
DON'T (option code) 254 Indicates the demand that the
|
||||
other party stop performing,
|
||||
or confirmation that you are no
|
||||
longer expecting the other party
|
||||
to perform, the indicated option.
|
||||
IAC 255 Data Byte 255.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 14]
|
||||
|
||||
|
||||
|
||||
RFC 854 May 1983
|
||||
|
||||
|
||||
CONNECTION ESTABLISHMENT
|
||||
|
||||
The TELNET TCP connection is established between the user's port U
|
||||
and the server's port L. The server listens on its well known port L
|
||||
for such connections. Since a TCP connection is full duplex and
|
||||
identified by the pair of ports, the server can engage in many
|
||||
simultaneous connections involving its port L and different user
|
||||
ports U.
|
||||
|
||||
Port Assignment
|
||||
|
||||
When used for remote user access to service hosts (i.e., remote
|
||||
terminal access) this protocol is assigned server port 23
|
||||
(27 octal). That is L=23.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Postel & Reynolds [Page 15]
|
||||
|
||||
48
src/bin/main.rs
Normal file
48
src/bin/main.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::io::{Error, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::thread;
|
||||
use telnet_server::read::Read;
|
||||
use telnet_server::telnet::{Session, State, StateConfig};
|
||||
|
||||
const BIND_ADDRESS: &str = "127.0.0.1:9000";
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let listener = TcpListener::bind(BIND_ADDRESS)?;
|
||||
|
||||
for stream in listener.incoming() {
|
||||
thread::spawn(move || {
|
||||
if let Ok(stream) = stream {
|
||||
let _ = handle_connection(stream);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connection(tcp_stream: TcpStream) -> Result<(), Error> {
|
||||
// Set up State and Session
|
||||
let state_config = StateConfig::default();
|
||||
let state = State::new(&state_config);
|
||||
let mut session = Session::new(state, tcp_stream)?;
|
||||
|
||||
// Make the Session listen to incoming TCP data in the background
|
||||
let session_listen = session.clone();
|
||||
let handle = thread::spawn(move || session_listen.listen());
|
||||
|
||||
loop {
|
||||
// Handle incoming TELNET messages:
|
||||
let incoming = session.read_line_waiting()?;
|
||||
let answer = format!("You sent: {incoming}");
|
||||
|
||||
if session.write_all(answer.as_bytes()).is_err() {
|
||||
break;
|
||||
}
|
||||
|
||||
if session.flush().is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handle.join().expect("Should await thread")
|
||||
}
|
||||
84
src/iter.rs
Normal file
84
src/iter.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
/// Returns whether the sequence `needle` is a part of `haystack`, regardless of
|
||||
/// its position.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `haystack` - The sequence to look for `needle`
|
||||
/// * `needle` - The sequence that may be a part of `haystack`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use telnet_server::iter::contains_sequence;
|
||||
/// // [1, 2, 3] is included in [1, 2, 3, 4, 5] -> returns true
|
||||
/// assert!(contains_sequence(&[1, 2, 3, 4, 5], &[1, 2, 3]));
|
||||
/// // [3, 3, 3] is *NOT* included in [1, 2, 3, 4, 5] -> returns false
|
||||
/// assert!(!contains_sequence(&[1, 2, 3, 4, 5], &[3, 3, 3]));
|
||||
/// ```
|
||||
pub fn contains_sequence<T: Eq>(haystack: &[T], needle: &[T]) -> bool {
|
||||
let haystack_len = haystack.len();
|
||||
let needle_len = needle.len();
|
||||
|
||||
if needle_len > haystack_len {
|
||||
return false;
|
||||
}
|
||||
|
||||
if haystack_len == 0 {
|
||||
/* this would be an incorrect edge case otherwise */
|
||||
return false;
|
||||
}
|
||||
|
||||
let size_diff = haystack_len - needle_len;
|
||||
|
||||
'outer: for haystack_start in 0..=size_diff {
|
||||
for needle_index in 0..needle_len {
|
||||
if haystack[needle_index + haystack_start] != needle[needle_index] {
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn finds_match_start() {
|
||||
assert!(contains_sequence(&[1, 2, 3, 4, 5], &[1, 2, 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_match_middle() {
|
||||
assert!(contains_sequence(&[1, 2, 3, 4, 5], &[2, 3, 4]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_match_end() {
|
||||
assert!(contains_sequence(&[1, 2, 3, 4, 5], &[3, 4, 5]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_no_match() {
|
||||
assert!(!contains_sequence(&[1, 2, 3, 4, 5], &[3, 3, 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_match_empty() {
|
||||
assert!(contains_sequence(&[1, 2, 3, 4, 5], &[]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_no_match_on_empty_haystack() {
|
||||
assert!(!contains_sequence(&[], &[1]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finds_no_match_on_empty_haystack_and_needle() {
|
||||
assert!(!contains_sequence::<u8>(&[], &[]));
|
||||
}
|
||||
}
|
||||
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub(crate) mod iter;
|
||||
|
||||
pub mod read;
|
||||
pub mod telnet;
|
||||
36
src/read.rs
Normal file
36
src/read.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
//! Module containing resources for reading data.
|
||||
//! The reason for this module to exist is "missing" - but mandatory -
|
||||
//! functionality in the [`std::io::Read`] trait.
|
||||
|
||||
use std::io::Error;
|
||||
|
||||
/// Trait for extending [`std::io::Read`] to add "missing" functionality
|
||||
pub trait Read {
|
||||
/// Reads line to a [`String`], ensuring it is never empty.
|
||||
/// Spins until a `\n` has been found, so that - even if the buffer is empty
|
||||
/// at some point - the result is always a non-empty line.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(String)` with a non-empty string, ending with `\n`
|
||||
/// * `Err(std::io::Error)` if reading fails
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use telnet_server::telnet::{Session, State, StateConfig};
|
||||
/// use crate::telnet_server::read::Read;
|
||||
///
|
||||
/// let mut session = Session::new(State::new(&StateConfig::default()), tcp_stream)?;
|
||||
///
|
||||
/// // set up session to receive data...
|
||||
///
|
||||
/// let incoming = session.read_line_waiting()?;
|
||||
///
|
||||
/// assert!(incoming.len() > 0);
|
||||
/// assert!(incoming.ends_with('\n'));
|
||||
///
|
||||
/// Ok(())
|
||||
/// ```
|
||||
fn read_line_waiting(&mut self) -> Result<String, Error>;
|
||||
}
|
||||
14
src/telnet/mod.rs
Normal file
14
src/telnet/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
//! TELNET module
|
||||
//!
|
||||
//! This module contains state and session handling for TCP connections to a
|
||||
//! TELNET service.
|
||||
//! It has two sub modules:
|
||||
//! * [`Session`] handles the TCP connection
|
||||
//! * [`State`] handles the internal (TELNET) state of an existing connection
|
||||
//! * [`StateConfig`] can be used to configure the handling of the [`State`]
|
||||
//! in specific cases.
|
||||
pub mod session;
|
||||
pub mod state;
|
||||
|
||||
pub use session::Session;
|
||||
pub use state::{State, StateConfig};
|
||||
150
src/telnet/session.rs
Normal file
150
src/telnet/session.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use super::State;
|
||||
use crate::read;
|
||||
use std::{
|
||||
io::{self, ErrorKind, Read, Result, Write},
|
||||
net::TcpStream,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// Handles the TCP connection for a TELNET service, allowing reading and
|
||||
/// writing access while also handling the internal TELNET state.
|
||||
///
|
||||
/// Implements [`std::io::Read`] and [`std::io::Write`] to receive and send
|
||||
/// messages from/to the connection.
|
||||
#[derive(Clone)]
|
||||
pub struct Session {
|
||||
/// Reference to a TELNET connection [`State`]
|
||||
state: Arc<Mutex<State>>,
|
||||
/// Refence to the TCP connection
|
||||
tcp_stream: Arc<Mutex<TcpStream>>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Creates new [`Session`] based on given [`TcpStream`] and a fresh
|
||||
/// [`State`].
|
||||
/// Also ensures that the TCP stream is non-blocking as otherwise the
|
||||
/// session becomes unusable.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `state` - A fresh [`State`]
|
||||
/// * `tcp_stream` - [`TcpStream`] for a TELNET based session. Notice that
|
||||
/// there's no instant check if this is for TELNET or anthother protocol.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(Self)` on success
|
||||
/// * `Err(std::io::Error)` if `tcp_stream` cannot be set to non-blocking
|
||||
pub fn new(state: State, tcp_stream: TcpStream) -> Result<Self> {
|
||||
tcp_stream.set_nonblocking(true)?;
|
||||
|
||||
Ok(Self {
|
||||
state: Arc::new(Mutex::new(state)),
|
||||
tcp_stream: Arc::new(Mutex::new(tcp_stream)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Listens to and handles incoming TCP data.
|
||||
/// Should be called in a background thread as it blocks. As the internal
|
||||
/// TCP stream is set to non-blocking, reading and writing on a cloned
|
||||
/// [`Session`] is still possible.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Only returns an `Err(std::io::Error)` on TCP errors as it runs
|
||||
/// indefinitely.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use std::thread;
|
||||
/// use telnet_server::telnet::{Session, State, StateConfig};
|
||||
///
|
||||
/// let mut session = Session::new(State::new(&StateConfig::default()), tcp_stream)?;
|
||||
///
|
||||
/// let session_listen = session.clone();
|
||||
/// let handle = thread::spawn(move || session_listen.listen());
|
||||
///
|
||||
/// // Send and receive messages here...
|
||||
///
|
||||
/// handle.join().expect("Should await thread");
|
||||
///
|
||||
/// Ok(())
|
||||
/// ```
|
||||
pub fn listen(self) -> Result<()> {
|
||||
let mut buf: [u8; 255] = [0; 255];
|
||||
|
||||
loop {
|
||||
let mut tcp_stream = match self.tcp_stream.try_lock() {
|
||||
Ok(t) => t,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
let tcp_data = match tcp_stream.read(&mut buf) {
|
||||
Ok(read_bytes) => &buf[..read_bytes],
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::WouldBlock {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(telnet_data) = self
|
||||
.state
|
||||
.lock()
|
||||
.expect("Should lock state")
|
||||
.write(tcp_data)?
|
||||
{
|
||||
tcp_stream.write_all(&telnet_data)?;
|
||||
tcp_stream.flush()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for Session {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
self.tcp_stream
|
||||
.lock()
|
||||
.expect("Should lock stream")
|
||||
.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
self.tcp_stream.lock().expect("Should lock stream").flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for Session {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
self.state.lock().expect("Should lock state").read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl read::Read for Session {
|
||||
fn read_line_waiting(&mut self) -> Result<String> {
|
||||
let mut line = String::new();
|
||||
let mut buf: [u8; 1] = [0];
|
||||
|
||||
loop {
|
||||
match self.read(&mut buf) {
|
||||
Ok(1) => {
|
||||
let next = buf[0] as char;
|
||||
line.push(next);
|
||||
if next == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(0) => {
|
||||
continue;
|
||||
}
|
||||
Ok(_) => panic!("Out of range"),
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(line)
|
||||
}
|
||||
}
|
||||
471
src/telnet/state.rs
Normal file
471
src/telnet/state.rs
Normal file
@@ -0,0 +1,471 @@
|
||||
use crate::iter::contains_sequence;
|
||||
use std::{
|
||||
cmp::min,
|
||||
io::{Error, Read},
|
||||
};
|
||||
|
||||
const ECHO: u8 = 1;
|
||||
const ERASE_LINE: u8 = 248;
|
||||
|
||||
const BEL: u8 = 7;
|
||||
|
||||
const CHAR_BACK_SPACE: u8 = 8;
|
||||
const CHAR_ESCAPE: u8 = 27;
|
||||
const CHAR_DELETE: u8 = 127;
|
||||
const CHAR_ERASE: u8 = 247;
|
||||
|
||||
const IAC: u8 = 255;
|
||||
/// "IAC SB"
|
||||
const IAC_SUBNEGOTIATION_START: u8 = 250;
|
||||
/// "IAC SE"
|
||||
const IAC_SUBNEGOTIATION_END: u8 = 240;
|
||||
const IAC_WILL: u8 = 251;
|
||||
const IAC_WONT: u8 = 252;
|
||||
const IAC_DO: u8 = 253;
|
||||
const IAC_DONT: u8 = 254;
|
||||
|
||||
/// Sequence for erasing current line in ANSI terminals
|
||||
const ANSI_SEQUENCE_ERASE_LINE: [u8; 5] = [CHAR_ESCAPE, 91, 50, 75, 13];
|
||||
|
||||
/// Any known ending character of ANSI escape sequences
|
||||
const CHARS_ESCAPE_SEQUENCE_END: [char; 20] = [
|
||||
'A', /* CUU */
|
||||
'B', /* CUD */
|
||||
'C', /* CUF */
|
||||
'D', /* CUB */
|
||||
'E', /* CNL */
|
||||
'F', /* CPL */
|
||||
'G', /* CHA */
|
||||
'H', /* CUP */
|
||||
'J', /* ED */
|
||||
'K', /* EL */
|
||||
'S', /* SU */
|
||||
'T', /* SD */
|
||||
'f', /* HVP */
|
||||
'm', /* SGR */
|
||||
'i', /* AUX */
|
||||
'n', /* DSR */
|
||||
's', /* SCP, SCOSC */
|
||||
'u', /* RCP, SCORC */
|
||||
'h', /* DECTCEM */
|
||||
'l', /* DECTCEM */
|
||||
];
|
||||
|
||||
const CHARS_LINE_BREAK: [u8; 2] = [b'\r', b'\n'];
|
||||
|
||||
/// Type for read-only bytes
|
||||
pub type Bytes = Box<[u8]>;
|
||||
pub type BytesResult = Result<Option<Bytes>, Error>;
|
||||
|
||||
/// Struct that holds and handles the current state of a TELNET session.
|
||||
/// Implements [`Read`] to get the handled readable, non-command data and a fake
|
||||
/// `Write` to accept incoming TCP data.
|
||||
///
|
||||
/// # Notice
|
||||
///
|
||||
/// This struct is independent of any service related input or TCP at all. It is
|
||||
/// just a bare state machine.
|
||||
pub struct State {
|
||||
/// Buffer of "readable" received data that can be obtained by using the
|
||||
/// [`Read`] trait
|
||||
output_buffer: Vec<u8>,
|
||||
/// Current overall mode
|
||||
mode: Mode,
|
||||
/// Indicates whether every incoming, non-command char should be echoed back
|
||||
/// to the connection
|
||||
is_echoing: bool,
|
||||
/// If true, ANSI escape sequences will be handled like normal non-command
|
||||
/// input. Otherwise, sequences will be ignored and a BEL is sent back to
|
||||
/// notice.
|
||||
handle_ansi_escape_sequences: bool,
|
||||
}
|
||||
|
||||
/// Configuration to set up a new [`State`]
|
||||
#[derive(Default)]
|
||||
pub struct StateConfig {
|
||||
/// If true, ANSI escape sequences will be handled like normal non-command
|
||||
/// input. Otherwise, sequences will be ignored and a BEL is sent back to
|
||||
/// notice.
|
||||
pub handle_ansi_escape_sequences: bool,
|
||||
}
|
||||
|
||||
/// Enumeration of overall modes that a TELNET state may have
|
||||
enum Mode {
|
||||
/// Incoming, non-command data (e.g. text)
|
||||
Idle,
|
||||
/// Incoming command data (e.g. WILL, WONT, DO, DONT)
|
||||
Command,
|
||||
/// Incoming command data for WILL command
|
||||
CommandWill,
|
||||
/// Incoming command data for WONT command
|
||||
CommandWont,
|
||||
/// Incoming command data for DO command
|
||||
CommandDo,
|
||||
/// Incoming command data for DONT command
|
||||
CommandDont,
|
||||
/// Incoming command data for sub negotiation command
|
||||
SubNegotiation,
|
||||
/// Incoming escape sequence. This is not a "real" mode but we need it as
|
||||
/// you can choose to ignore ANSI escape sequences because it doesn't really
|
||||
/// make sense to evaluate these.
|
||||
AnsiEscapeSequence,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`] with given configuration
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - [`StateConfig`] to set up the behaviour of the resutlting
|
||||
/// [`State`]
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Fresh [`State`] for a TELNET session
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use telnet_server::telnet::{StateConfig, State};
|
||||
///
|
||||
/// let config = StateConfig::default();
|
||||
/// let state = State::new(&config);
|
||||
/// ```
|
||||
pub fn new(config: &StateConfig) -> Self {
|
||||
State {
|
||||
output_buffer: vec![],
|
||||
mode: Mode::Idle,
|
||||
is_echoing: false,
|
||||
handle_ansi_escape_sequences: config.handle_ansi_escape_sequences,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a buffer of incoming TELNET data into the state, returning an
|
||||
/// immediate response to the other part. Consumes the whole buffer _unless_
|
||||
/// there's an error (e.g. invalid data) which immediatelly results in an
|
||||
/// [`Error`].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `data` - Incoming TELNET data
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Err(std::io::Error)` if an error occurs. In this case the internal
|
||||
/// TELNET state possibly mismatches the "real" state. This _should_ lead
|
||||
/// to the termination of the TELNET session at all.
|
||||
/// * `Ok(Some([u8]))` if data should be sent back
|
||||
/// * `Ok(None)` if everythings fine
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use telnet_server::telnet::{StateConfig, State};
|
||||
///
|
||||
/// let config = StateConfig::default();
|
||||
/// let mut state = State::new(&config);
|
||||
///
|
||||
/// let data = [255, 253, 1];
|
||||
/// let result = state.write(&data)?;
|
||||
///
|
||||
/// assert!(result.is_some());
|
||||
/// let result = result.unwrap();
|
||||
///
|
||||
/// // write back result to TCP connection...
|
||||
///
|
||||
/// Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
pub fn write(&mut self, buf: &[u8]) -> BytesResult {
|
||||
let mut response: Vec<u8> = vec![];
|
||||
|
||||
for &next in buf {
|
||||
let result = match self.mode {
|
||||
Mode::Idle => self.next_on_idle(next),
|
||||
Mode::Command => self.next_as_command(next),
|
||||
Mode::CommandWill => self.next_as_will(next),
|
||||
Mode::CommandWont => self.next_as_wont(next),
|
||||
Mode::CommandDo => self.next_as_do(next),
|
||||
Mode::CommandDont => self.next_as_dont(next),
|
||||
Mode::SubNegotiation => self.next_as_sub_negotiation(next),
|
||||
Mode::AnsiEscapeSequence => self.next_as_escape_sequence(next),
|
||||
};
|
||||
|
||||
if let Ok(Some(v)) = result {
|
||||
response.extend_from_slice(&v);
|
||||
} else if result.is_err() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if !response.is_empty() {
|
||||
Ok(Some(response.into_boxed_slice()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in idle mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_on_idle(&mut self, next: u8) -> BytesResult {
|
||||
match next {
|
||||
IAC => self.mode = Mode::Command,
|
||||
CHAR_DELETE | CHAR_BACK_SPACE | CHAR_ERASE => {
|
||||
self.output_buffer.pop();
|
||||
|
||||
if self.is_echoing {
|
||||
/* Return fake backspace on echo mode */
|
||||
return Ok(Some(Box::new([CHAR_BACK_SPACE, b' ', CHAR_BACK_SPACE])));
|
||||
}
|
||||
}
|
||||
ERASE_LINE => {
|
||||
Self::erase_current_line(&mut self.output_buffer);
|
||||
|
||||
if self.is_echoing {
|
||||
return Ok(Some(ANSI_SEQUENCE_ERASE_LINE.into()));
|
||||
}
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
CHAR_ESCAPE => {
|
||||
self.mode = Mode::AnsiEscapeSequence;
|
||||
|
||||
if self.is_echoing {
|
||||
return Ok(Some(Box::new([next])));
|
||||
}
|
||||
|
||||
if self.handle_ansi_escape_sequences {
|
||||
self.output_buffer.push(next);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.output_buffer.push(next);
|
||||
|
||||
if self.is_echoing {
|
||||
return Ok(Some(Box::new([next])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in IAC mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_as_command(&mut self, next: u8) -> BytesResult {
|
||||
match next {
|
||||
IAC_WILL => self.mode = Mode::CommandWill,
|
||||
IAC_WONT => self.mode = Mode::CommandWont,
|
||||
IAC_DO => self.mode = Mode::CommandDo,
|
||||
IAC_DONT => self.mode = Mode::CommandDont,
|
||||
IAC_SUBNEGOTIATION_START => self.mode = Mode::SubNegotiation,
|
||||
_ => {
|
||||
return Err(Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("Unknown command '{next}'"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in IAC WILL mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_as_will(&mut self, _next: u8) -> BytesResult {
|
||||
/* Ignore message, just go back to idle state */
|
||||
self.mode = Mode::Idle;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in IAC WONT mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_as_wont(&mut self, _next: u8) -> BytesResult {
|
||||
/* Ignore message, just go back to idle state */
|
||||
self.mode = Mode::Idle;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in IAC DO mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_as_do(&mut self, next: u8) -> BytesResult {
|
||||
self.mode = Mode::Idle;
|
||||
|
||||
if next == ECHO {
|
||||
self.is_echoing = true;
|
||||
return Ok(Some(Box::new([IAC, IAC_WILL, ECHO])));
|
||||
}
|
||||
|
||||
/* Whatever they're asking for, we're not supporting it probably. */
|
||||
Ok(Some(Box::new([IAC, IAC_WONT, next])))
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in IAC DONT mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_as_dont(&mut self, next: u8) -> BytesResult {
|
||||
self.mode = Mode::Idle;
|
||||
|
||||
if next == ECHO {
|
||||
self.is_echoing = false;
|
||||
}
|
||||
|
||||
/* Whatever they're asking for, we're not supporting it probably.
|
||||
* So it's fine to say that we won't do it. */
|
||||
Ok(Some(Box::new([IAC, IAC_WONT, next])))
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in IAC SB mode
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_as_sub_negotiation(&mut self, next: u8) -> BytesResult {
|
||||
/* We're NOT handling sub negotiations right now. */
|
||||
if next == IAC_SUBNEGOTIATION_END {
|
||||
self.mode = Mode::Idle;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Handles incoming `next` byte when [`State`] is in idle mode and in an
|
||||
/// ANSI escape sequence
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(None)` - Everythings okay, no need to write something back
|
||||
/// * `Ok(Some(Bytes))` - Everythings okay, something has to be written back
|
||||
/// * `Err` - Data could not be interpreted
|
||||
fn next_as_escape_sequence(&mut self, next: u8) -> BytesResult {
|
||||
if self.handle_ansi_escape_sequences {
|
||||
self.output_buffer.push(next);
|
||||
|
||||
if CHARS_ESCAPE_SEQUENCE_END.contains(&(next as char)) {
|
||||
self.mode = Mode::Idle;
|
||||
}
|
||||
|
||||
if self.is_echoing {
|
||||
Ok(Some(Box::new([next])))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
if !CHARS_ESCAPE_SEQUENCE_END.contains(&(next as char)) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.mode = Mode::Idle;
|
||||
|
||||
Ok(Some(Box::new([BEL])))
|
||||
}
|
||||
}
|
||||
|
||||
/// Erases the current line from given text buffer. According to
|
||||
/// [RFC-854](https://www.rfc-editor.org/rfc/rfc854#page-13), the last
|
||||
/// CR LF should be kept.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `buffer` - Text buffer that should be updated. All current line
|
||||
/// characters will be removed from the [`Vec`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use telnet_server::telnet::State;
|
||||
///
|
||||
/// let mut buffer = vec![b'a', b'b', b'c', b'\r', b'\n', b'd', b'e', b'f'];
|
||||
/// State::erase_current_line(&mut buffer);
|
||||
/// assert_eq!(buffer, [b'a', b'b', b'c', b'\r', b'\n']);
|
||||
///
|
||||
/// State::erase_current_line(&mut buffer);
|
||||
/// assert_eq!(buffer, [b'a', b'b', b'c', b'\r', b'\n']);
|
||||
///
|
||||
/// let mut buffer = vec![b'a', b'b', b'c', b'd', b'e', b'f'];
|
||||
/// State::erase_current_line(&mut buffer);
|
||||
/// assert!(buffer.is_empty());
|
||||
/// ```
|
||||
fn erase_current_line(buffer: &mut Vec<u8>) {
|
||||
loop {
|
||||
let buffer_len = buffer.len();
|
||||
|
||||
/* Remove all chars until \r\n reached */
|
||||
if buffer_len < 2 {
|
||||
buffer.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
let start_index = buffer_len - 2;
|
||||
if contains_sequence(&buffer[start_index..], &CHARS_LINE_BREAK) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for State {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let limit = min(buf.len(), self.output_buffer.len());
|
||||
|
||||
let (left, _) = buf.split_at_mut(limit);
|
||||
left.copy_from_slice(&self.output_buffer[..limit]);
|
||||
self.output_buffer.drain(..limit);
|
||||
|
||||
Ok(limit)
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn erase_current_line_should_work() {
|
||||
let mut buffer = vec![b'a', b'b', b'c', b'\r', b'\n', b'd', b'e', b'f'];
|
||||
State::erase_current_line(&mut buffer);
|
||||
/* RFC-854: 'The recipient should delete characters from the data stream
|
||||
* back to, but not including, the last "CR LF" sequence sent over the
|
||||
* TELNET connection.' */
|
||||
assert_eq!(buffer, [b'a', b'b', b'c', b'\r', b'\n']);
|
||||
|
||||
State::erase_current_line(&mut buffer);
|
||||
assert_eq!(buffer, [b'a', b'b', b'c', b'\r', b'\n']);
|
||||
|
||||
let mut buffer = vec![b'a', b'b', b'c', b'd', b'e', b'f'];
|
||||
State::erase_current_line(&mut buffer);
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
}
|
||||
5
telnet/Dockerfile
Normal file
5
telnet/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM alpine
|
||||
|
||||
RUN apk update && apk add busybox-extras
|
||||
|
||||
ENTRYPOINT ["/usr/bin/telnet"]
|
||||
7
telnet/run_telnet.sh
Executable file
7
telnet/run_telnet.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -z "$(docker images -q telnet_docker:latest 2> /dev/null)" ]; then
|
||||
docker build --tag telnet_docker . --file "$(dirname "$0")/Dockerfile"
|
||||
fi
|
||||
|
||||
docker run --rm -it telnet_docker:latest "$@"
|
||||
Reference in New Issue
Block a user