mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-16 06:28:38 +03:00
Squashed 'outside/anachronism/' content from commit 02fa3b0
git-subtree-dir: outside/anachronism git-subtree-split: 02fa3b064112fa1d75209f123c07b5c7d58329b7
This commit is contained in:
commit
7536d1406f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build/
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2010 Jonathan Castello
|
||||
|
||||
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.
|
65
Makefile
Normal file
65
Makefile
Normal file
@ -0,0 +1,65 @@
|
||||
SHELL = sh
|
||||
|
||||
CC = gcc
|
||||
FLAGS = -c -fPIC -Iinclude/
|
||||
CFLAGS = --pedantic -Wall -Wextra -march=native -std=gnu99
|
||||
INCLUDE = include/anachronism
|
||||
|
||||
VERSION_MAJOR = 0
|
||||
VERSION = $(VERSION_MAJOR).3.1
|
||||
|
||||
SO = libanachronism.so
|
||||
SOFILE = $(SO).$(VERSION)
|
||||
SONAME = $(SO).$(VERSION_MAJOR)
|
||||
|
||||
|
||||
all: static shared
|
||||
shared: build/ build/$(SOFILE)
|
||||
static: build/ build/libanachronism.a
|
||||
|
||||
build/:
|
||||
mkdir build
|
||||
|
||||
build/$(SOFILE): build/nvt.o build/parser.o
|
||||
$(CC) -shared -Wl,-soname,$(SONAME) -o build/$(SOFILE) build/nvt.o build/parser.o
|
||||
|
||||
build/libanachronism.a: build/nvt.o build/parser.o
|
||||
ar rcs build/libanachronism.a build/nvt.o build/parser.o
|
||||
|
||||
build/nvt.o: src/nvt.c $(INCLUDE)/nvt.h $(INCLUDE)/common.h
|
||||
$(CC) $(FLAGS) $(CFLAGS) src/nvt.c -o build/nvt.o
|
||||
|
||||
build/parser.o: src/parser.c $(INCLUDE)/parser.h $(INCLUDE)/common.h
|
||||
$(CC) $(FLAGS) $(CFLAGS) src/parser.c -o build/parser.o
|
||||
|
||||
src/parser.c: src/parser.rl src/parser_common.rl
|
||||
ragel -C -G2 src/parser.rl -o src/parser.c
|
||||
|
||||
|
||||
graph: doc/parser.png
|
||||
|
||||
doc/parser.png: src/parser.rl src/parser_common.rl
|
||||
ragel -V -p src/parser.rl | dot -Tpng > doc/parser.png
|
||||
|
||||
install: all
|
||||
install -D -d /usr/local/include/anachronism/ /usr/local/lib
|
||||
install -D include/anachronism/* /usr/local/include/anachronism/
|
||||
install -D build/$(SOFILE) /usr/local/lib/$(SOFILE)
|
||||
install -D build/libanachronism.a /usr/local/lib/libanachronism.a
|
||||
ln -s -f /usr/local/lib/$(SOFILE) /usr/local/lib/$(SONAME)
|
||||
ln -s -f /usr/local/lib/$(SOFILE) /usr/local/lib/$(SO)
|
||||
|
||||
uninstall:
|
||||
-rm -rf /usr/local/include/anachronism
|
||||
-rm /usr/local/lib/libanachronism.a
|
||||
-rm /usr/local/lib/$(SOFILE)
|
||||
-rm /usr/local/lib/$(SONAME)
|
||||
-rm /usr/local/lib/$(SO)
|
||||
|
||||
clean:
|
||||
-rm -f build/nvt.o build/router.o build/parser.o
|
||||
|
||||
distclean: clean
|
||||
-rm -f build/libanachronism.a build/$(SOFILE)
|
||||
|
||||
.PHONY: all static shared clean distclean install uninstall
|
158
README.md
Normal file
158
README.md
Normal file
@ -0,0 +1,158 @@
|
||||
# Anachronism
|
||||
Anachronism is a fully-compliant implementation of [the Telnet protocol][wiki-telnet]. Fallen
|
||||
out of favor in this day and age, most people only know it as a command-line
|
||||
tool for debugging HTTP. Today, Telnet is most commonly used in the realm of
|
||||
[MUDs][wiki-muds], though there are still a few other niches filled by Telnet.
|
||||
|
||||
Anachronism offers a simple API for translating between streams of data and
|
||||
events, and is completely network-agnostic. Anachronism also offers **channels**, an
|
||||
abstraction layer which treats Telnet as a data multiplexer. Channels make it
|
||||
extremely easy to build reusable modules for Telnet sub-protocols such
|
||||
as MCCP (MUD Client Compression Protocol), which can be written once and plugged
|
||||
into any application that wants to include support.
|
||||
|
||||
[wiki-telnet]: http://en.wikipedia.org/wiki/Telnet (Telnet at Wikipedia)
|
||||
[wiki-muds]: http://en.wikipedia.org/wiki/MUD (MUDs at Wikipedia)
|
||||
|
||||
## Installation
|
||||
While Anachronism has no dependencies and is theoretically cross-platform, I've
|
||||
only written a Makefile for Linux. Help would be appreciated for making this
|
||||
work across more platforms.
|
||||
|
||||
make
|
||||
sudo make install
|
||||
|
||||
This will install Anachronism's shared and static libraries to /usr/local/lib,
|
||||
and its header files to /usr/local/include/anachronism/. You may also need to
|
||||
run `ldconfig` to make Anachronism available to your project's compiler/linker.
|
||||
|
||||
## Usage
|
||||
The anachronism/nvt.h header can be consulted for more complete documentation.
|
||||
|
||||
### Basic usage
|
||||
The core type exposed by Anachronism is the telnet\_nvt, which represents the
|
||||
Telnet RFC's "Network Virtual Terminal". An NVT is created using
|
||||
telnet\_nvt\_new(). When creating an NVT, you must provide it with a set of
|
||||
callbacks to send events to, and an optional void\* to store as the event
|
||||
handler's context. You can use telnet\_recv() to process incoming data, and
|
||||
the telnet\_send\_\*() set of functions to emit outgoing data.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <anachronism/nvt.h>
|
||||
|
||||
void on_event(telnet_nvt* nvt, telnet_event* event)
|
||||
{
|
||||
switch (event->type)
|
||||
{
|
||||
// A data event (normal text received)
|
||||
case TELNET_EV_DATA:
|
||||
{
|
||||
telnet_data_event* ev = (telnet_data_event*)event;
|
||||
printf("[IN]: %.*s\n", ev->length, ev->data);
|
||||
break;
|
||||
}
|
||||
|
||||
// Outgoing data emitted by the NVT
|
||||
case TELNET_EV_SEND:
|
||||
{
|
||||
telnet_send_event* ev = (telnet_send_event*)event;
|
||||
printf("[OUT]: %.*s\n", ev->length, ev->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// Create an NVT
|
||||
telnet_nvt* nvt = telnet_nvt_new(NULL, &on_event, NULL, NULL);
|
||||
|
||||
// Process some incoming data
|
||||
const char* data = "foo bar baz";
|
||||
telnet_receive(nvt, (const telnet_byte*)data, strlen(data), NULL);
|
||||
|
||||
// Free the NVT
|
||||
telnet_nvt_free(nvt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
### Telopts
|
||||
Anachronism provides an easy-to-use interface to Telnet's "telopt" functionality
|
||||
via the telnet\_telopt\_*() set of functions. As telopts are negotiated and
|
||||
utilized, events are sent to the telopt callback provided to telnet_nvt_new().
|
||||
|
||||
#include <stdio.h>
|
||||
#include <anachronism/nvt.h>
|
||||
|
||||
void on_event(telnet_nvt* nvt, telnet_event* event)
|
||||
{
|
||||
switch (event->type)
|
||||
{
|
||||
// Outgoing data emitted by the NVT
|
||||
case TELNET_EV_SEND:
|
||||
{
|
||||
telnet_send_event* ev = (telnet_send_event*)event;
|
||||
printf("[OUT]: %.*s\n", ev->length, ev->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_telopt_event(telnet_nvt* nvt, telnet_byte telopt, telnet_telopt_event* event)
|
||||
{
|
||||
// telopt is the telopt this event was triggered for
|
||||
|
||||
switch (event->type)
|
||||
{
|
||||
case TELNET_EV_TELOPT_TOGGLE:
|
||||
telnet_telopt_toggle_event* ev = (telnet_telopt_toggle_event*)event;
|
||||
// ev->where is TELNET_TELOPT_LOCAL or TELNET_TELOPT_REMOTE,
|
||||
// corresponding to Telnet's WILL/WONT and DO/DONT commands.
|
||||
// ev->status is TELNET_TELOPT_ON or TELNET_TELOPT_OFF.
|
||||
break;
|
||||
case TELNET_EV_TELOPT_FOCUS:
|
||||
telnet_telopt_focus_event* ev = (telnet_telopt_focus_event*)event;
|
||||
// ev->focus is 1 or 0 depending on if a subnegotiation packet has
|
||||
// begun or ended.
|
||||
break;
|
||||
case TELNET_EV_TELOPT_DATA:
|
||||
telnet_telopt_data_event* ev = (telnet_telopt_data_event*)event;
|
||||
// ev->data is a pointer to the received data.
|
||||
// ev->length is the length of the data buffer.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// Create an NVT
|
||||
telnet_nvt* nvt = telnet_nvt_new(NULL, &on_event, &on_telopt_event, NULL);
|
||||
|
||||
// Ask to enable a telopt locally (a WILL command)
|
||||
telnet_request_enable(nvt, 230, TELNET_LOCAL);
|
||||
|
||||
// Process some incoming data
|
||||
const char* data = "\xFF\xFD\xE6" // IAC DO 230 (turn channel on)
|
||||
"\xFF\xFA\xE6" // IAC SB 230 (switch to channel)
|
||||
"foo bar baz" (send data)
|
||||
"\xFF\xF0"; // IAC SE (switch to main)
|
||||
telnet_receive(nvt, (const telnet_byte*)data, strlen(data), NULL);
|
||||
|
||||
// Free the NVT
|
||||
telnet_nvt_free(nvt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
### Interrupting
|
||||
TODO: Explain how to interrupt the parser.
|
||||
|
||||
## Alternatives
|
||||
* [libtelnet][github-libtelnet], by Elanthis<br>
|
||||
It incorporates a number of (rather MUD-specific) protocols by default,
|
||||
though its API is quite different.
|
||||
|
||||
[github-libtelnet]: https://github.com/elanthis/libtelnet (libtelnet on GitHub)
|
||||
|
||||
## Credits
|
||||
Someone from #startups on Freenode IRC suggested the name (I'm sure as a joke).
|
||||
If you read this, remind me who you are so I can credit you properly!
|
50
doc/channels.md
Normal file
50
doc/channels.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Telnet
|
||||
|
||||
## Channels
|
||||
Telnet supports data multiplexing by way of 256 built-in sub-channels, each
|
||||
identified by a byte in the interval [\x00-\xFF]. By switching between
|
||||
channels, you can send completely separate streams of data through the same
|
||||
connection.
|
||||
|
||||
All channels start out closed by default. To open a channel, one host must
|
||||
request or offer a channel using IAC WILL <id> or IAC DO <id>. The remote host
|
||||
then responds with IAC DO <id> or IAC WILL <id>, respectively. Alternatively,
|
||||
the request may be denied using IAC DONT <id> or IAC WONT <id>, respectively.
|
||||
|
||||
In order to switch to a specific channel, the IAC SB <id> sequence must
|
||||
be used. All data sent afterwards will be routed through that specific channel.
|
||||
To switch back to the main channel, IAC SE must be used. Note that subchannels
|
||||
do not support any IAC sequences except IAC IAC (an escaped \xFF byte) and
|
||||
IAC SE (return to the main channel). In particular, you cannot switch directly
|
||||
from one subchannel to another: you must revert to the main channel first.
|
||||
|
||||
Due to the unbiased nature of Telnet, neither side of the connection is
|
||||
automatically recognized as the server or the client. However, a host may either
|
||||
request a channel (as a client) or offer a channel (as a server). The WILL/WONT
|
||||
commands are used in the role of server ("I will", "I wont"), while DO/DONT
|
||||
are used in the role of client ("You do", "You do not"). As such, a channel
|
||||
may be opened twice (even simultaneously).
|
||||
|
||||
As an example, lets assume a terminal is connected to a server using Telnet. The
|
||||
server offers MCCP (data compression), but wants to know what the terminal's
|
||||
window size is. The following communication might occur:
|
||||
|
||||
<server> IAC DO NAWS
|
||||
<server> IAC WILL MCCP
|
||||
<client> IAC WILL NAWS
|
||||
<client> IAC SB NAWS \x50 \x00 \x50 \x00 IAC SE
|
||||
<client> IAC DO MCCP
|
||||
<server> IAC SB MCCP IAC SE
|
||||
<server> (compressed data)
|
||||
|
||||
Notice that MCCP was negotiated such that the server offers the compression.
|
||||
Only the server-to-client flow of data is compressed; the client would not
|
||||
compress its data unless the channel was negotiated in the other direction as
|
||||
well.
|
||||
|
||||
In general, a specific subchannel is tied to a specific Telnet subprotocol. For
|
||||
example, the EXOPL subprotocol is assigned to channel 255, so that channel
|
||||
should be avoided for any other purpose. A full list of registered subprotocols
|
||||
can be found on the [IANA website][1].
|
||||
|
||||
[1]: http://www.iana.org/assignments/telnet-options
|
BIN
doc/parser.png
Normal file
BIN
doc/parser.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
24
include/anachronism/common.h
Normal file
24
include/anachronism/common.h
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef ANACHRONISM_COMMON_H
|
||||
#define ANACHRONISM_COMMON_H
|
||||
|
||||
#include <stdlib.h> /* for size_t */
|
||||
|
||||
// Telnet bytes must be unsigned
|
||||
typedef unsigned char telnet_byte;
|
||||
|
||||
// Error codes returned from API functions
|
||||
// Positive codes are success/notice codes.
|
||||
// Nonpositive codes are errors.
|
||||
// ALLOC is 0 for parity with the NULL result from malloc().
|
||||
typedef enum telnet_error
|
||||
{
|
||||
TELNET_E_NOT_SUBNEGOTIABLE = -4, // The telopt is not open for subnegotiation.
|
||||
TELNET_E_BAD_PARSER = -3, // The telnet_parser* passed is NULL
|
||||
TELNET_E_BAD_NVT = -2, // The telnet_nvt* passed is NULL
|
||||
TELNET_E_INVALID_COMMAND = -1, // The telnet_byte passed is not an allowed command in this API method
|
||||
TELNET_E_ALLOC = 0, // Not enough memory to allocate essential library structures
|
||||
TELNET_E_OK = 1, // Huge Success!
|
||||
TELNET_E_INTERRUPT = 2, // Parser interrupted by user code.
|
||||
} telnet_error;
|
||||
|
||||
#endif // ANACHRONISM_COMMON_H
|
214
include/anachronism/nvt.h
Normal file
214
include/anachronism/nvt.h
Normal file
@ -0,0 +1,214 @@
|
||||
#ifndef ANACHRONISM_ANACHRONISM_H
|
||||
#define ANACHRONISM_ANACHRONISM_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <anachronism/common.h>
|
||||
|
||||
// predefined Telnet commands from 240-255
|
||||
enum
|
||||
{
|
||||
IAC_SE = 240,
|
||||
IAC_NOP,
|
||||
IAC_DM,
|
||||
IAC_BRK,
|
||||
IAC_IP,
|
||||
IAC_AO,
|
||||
IAC_AYT,
|
||||
IAC_EC,
|
||||
IAC_EL,
|
||||
IAC_GA,
|
||||
IAC_SB,
|
||||
IAC_WILL,
|
||||
IAC_WONT,
|
||||
IAC_DO,
|
||||
IAC_DONT,
|
||||
IAC_IAC,
|
||||
};
|
||||
|
||||
typedef enum telnet_telopt_location
|
||||
{
|
||||
TELNET_LOCAL,
|
||||
TELNET_REMOTE,
|
||||
} telnet_telopt_location;
|
||||
|
||||
|
||||
/**
|
||||
* NVT Events
|
||||
*/
|
||||
|
||||
typedef enum telnet_event_type
|
||||
{
|
||||
TELNET_EV_DATA, /* A stretch of plain data was received. (data, length) */
|
||||
TELNET_EV_COMMAND, /* A simple IAC comamnd was recevied. (command) */
|
||||
TELNET_EV_WARNING, /* A non-fatal invalid sequence was received. (message, position) */
|
||||
TELNET_EV_SEND, /* Outgoing data to be sent. (data, length) */
|
||||
} telnet_event_type;
|
||||
|
||||
typedef struct telnet_event
|
||||
{
|
||||
telnet_event_type type;
|
||||
} telnet_event;
|
||||
|
||||
typedef struct telnet_data_event
|
||||
{
|
||||
telnet_event SUPER_;
|
||||
const telnet_byte* data;
|
||||
size_t length;
|
||||
} telnet_data_event;
|
||||
|
||||
typedef struct telnet_command_event
|
||||
{
|
||||
telnet_event SUPER_;
|
||||
telnet_byte command;
|
||||
} telnet_command_event;
|
||||
|
||||
typedef struct telnet_warning_event
|
||||
{
|
||||
telnet_event SUPER_;
|
||||
const char* message;
|
||||
size_t position;
|
||||
} telnet_warning_event;
|
||||
|
||||
typedef struct telnet_send_event
|
||||
{
|
||||
telnet_event SUPER_;
|
||||
const telnet_byte* data;
|
||||
size_t length;
|
||||
} telnet_send_event;
|
||||
|
||||
|
||||
/**
|
||||
* Telopt Events
|
||||
*/
|
||||
|
||||
typedef enum telnet_telopt_event_type
|
||||
{
|
||||
TELNET_EV_TELOPT_TOGGLE,
|
||||
TELNET_EV_TELOPT_FOCUS,
|
||||
TELNET_EV_TELOPT_DATA,
|
||||
} telnet_telopt_event_type;
|
||||
|
||||
typedef struct telnet_telopt_event
|
||||
{
|
||||
telnet_telopt_event_type type;
|
||||
} telnet_telopt_event;
|
||||
|
||||
typedef struct telnet_telopt_toggle_event
|
||||
{
|
||||
telnet_telopt_event SUPER_;
|
||||
telnet_telopt_location where;
|
||||
unsigned char status;
|
||||
} telnet_telopt_toggle_event;
|
||||
|
||||
typedef struct telnet_telopt_focus_event
|
||||
{
|
||||
telnet_telopt_event SUPER_;
|
||||
unsigned char focus;
|
||||
} telnet_telopt_focus_event;
|
||||
|
||||
typedef struct telnet_telopt_data_event
|
||||
{
|
||||
telnet_telopt_event SUPER_;
|
||||
const telnet_byte* data;
|
||||
size_t length;
|
||||
} telnet_telopt_data_event;
|
||||
|
||||
|
||||
|
||||
typedef struct telnet_nvt telnet_nvt;
|
||||
|
||||
|
||||
typedef void (*telnet_nvt_event_callback)(telnet_nvt* nvt, telnet_event* event);
|
||||
typedef void (*telnet_telopt_event_callback)(telnet_nvt* nvt, telnet_byte telopt, telnet_telopt_event* event);
|
||||
typedef unsigned char (*telnet_negotiate_event_callback)(telnet_nvt* nvt, telnet_byte telopt, telnet_telopt_location where);
|
||||
|
||||
/**
|
||||
Creates a new Telnet NVT.
|
||||
|
||||
Errors:
|
||||
TELNET_E_ALLOC - Unable to allocate enough memory for the NVT.
|
||||
*/
|
||||
telnet_nvt* telnet_nvt_new(void* userdata,
|
||||
telnet_nvt_event_callback nvt_callback,
|
||||
telnet_telopt_event_callback telopt_callback,
|
||||
telnet_negotiate_event_callback negotiate_callback);
|
||||
|
||||
void telnet_nvt_free(telnet_nvt* nvt);
|
||||
|
||||
/**
|
||||
Every NVT can have some user-specific data attached, such as a user-defined struct.
|
||||
This can be accessed (primarily by event callbacks) to differentiate between NVTs.
|
||||
|
||||
Errors:
|
||||
TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter.
|
||||
|
||||
Example:
|
||||
// assuming a FILE was passed to telnet_nvt_new():
|
||||
FILE out = NULL;
|
||||
telnet_get_userdata(nvt, (void**)&out);
|
||||
*/
|
||||
telnet_error telnet_get_userdata(telnet_nvt* nvt, void** udata);
|
||||
|
||||
/**
|
||||
Processes incoming data.
|
||||
If `bytes_used` is non-NULL, it will be set to the length of the string that
|
||||
was read. This is generally only useful if you use telnet_halt() in a callback.
|
||||
|
||||
Errors:
|
||||
TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter.
|
||||
TELNET_E_ALLOC - Unable to allocate destination buffer for incoming text.
|
||||
TELNET_E_INTERRUPT - User code interrupted the parser.
|
||||
*/
|
||||
telnet_error telnet_receive(telnet_nvt* nvt, const telnet_byte* data, size_t length, size_t* bytes_used);
|
||||
|
||||
/**
|
||||
If currently parsing (i.e. telnet_recv() is running), interrupts the parser.
|
||||
This is useful for things such as MCCP, where a Telnet sequence hails the start of
|
||||
data that must be decompressed before being parsed.
|
||||
|
||||
Errors:
|
||||
TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter.
|
||||
*/
|
||||
telnet_error telnet_interrupt(telnet_nvt* nvt);
|
||||
|
||||
|
||||
/**
|
||||
Sends a string as a stream of escaped Telnet data.
|
||||
|
||||
Errors:
|
||||
TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter.
|
||||
TELNET_E_ALLOC - Unable to allocate destination buffer for outgoing text.
|
||||
*/
|
||||
telnet_error telnet_send_data(telnet_nvt* nvt, const telnet_byte* data, const size_t length);
|
||||
|
||||
/**
|
||||
Sends a Telnet command.
|
||||
|
||||
Errors:
|
||||
TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter.
|
||||
TELNET_E_INVALID_COMMAND - The command cannot be WILL, WONT, DO, DONT, SB, or SE.
|
||||
*/
|
||||
telnet_error telnet_send_command(telnet_nvt* nvt, const telnet_byte command);
|
||||
|
||||
/**
|
||||
Sends a subnegotiation packet.
|
||||
|
||||
Errors:
|
||||
TELNET_E_BAD_NVT - Invalid telnet_nvt* parameter.
|
||||
TELNET_E_ALLOC - Unable to allocate destination buffer for outgoing text.
|
||||
*/
|
||||
telnet_error telnet_send_subnegotiation(telnet_nvt* nvt, const telnet_byte option, const telnet_byte* data, const size_t length);
|
||||
|
||||
|
||||
telnet_error telnet_telopt_enable(telnet_nvt* nvt, const telnet_byte telopt, telnet_telopt_location where);
|
||||
telnet_error telnet_telopt_disable(telnet_nvt* nvt, const telnet_byte telopt, telnet_telopt_location where);
|
||||
telnet_error telnet_telopt_status(telnet_nvt* nvt, const telnet_byte telopt, telnet_telopt_location where, unsigned char* status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ANACHRONISM_ANACHRONISM_H
|
73
include/anachronism/parser.h
Normal file
73
include/anachronism/parser.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef ANACHRONISM_PARSER_H
|
||||
#define ANACHRONISM_PARSER_H
|
||||
|
||||
#include <anachronism/common.h>
|
||||
|
||||
typedef enum telnet_parser_event_type
|
||||
{
|
||||
TELNET_EV_PARSER_DATA,
|
||||
TELNET_EV_PARSER_COMMAND,
|
||||
TELNET_EV_PARSER_OPTION,
|
||||
TELNET_EV_PARSER_SUBNEGOTIATION,
|
||||
TELNET_EV_PARSER_WARNING,
|
||||
} telnet_parser_event_type;
|
||||
|
||||
typedef struct telnet_parser_event
|
||||
{
|
||||
telnet_parser_event_type type;
|
||||
} telnet_parser_event;
|
||||
|
||||
typedef struct telnet_parser_data_event
|
||||
{
|
||||
telnet_parser_event SUPER_;
|
||||
const telnet_byte* data;
|
||||
size_t length;
|
||||
} telnet_parser_data_event;
|
||||
|
||||
typedef struct telnet_parser_command_event
|
||||
{
|
||||
telnet_parser_event SUPER_;
|
||||
telnet_byte command;
|
||||
} telnet_parser_command_event;
|
||||
|
||||
typedef struct telnet_parser_option_event
|
||||
{
|
||||
telnet_parser_event SUPER_;
|
||||
telnet_byte command;
|
||||
telnet_byte option;
|
||||
} telnet_parser_option_event;
|
||||
|
||||
typedef struct telnet_parser_subnegotiation_event
|
||||
{
|
||||
telnet_parser_event SUPER_;
|
||||
int active;
|
||||
telnet_byte option;
|
||||
} telnet_parser_subnegotiation_event;
|
||||
|
||||
typedef struct telnet_parser_warning_event
|
||||
{
|
||||
telnet_parser_event SUPER_;
|
||||
const char* message;
|
||||
size_t position;
|
||||
} telnet_parser_warning_event;
|
||||
|
||||
|
||||
|
||||
typedef struct telnet_parser telnet_parser;
|
||||
|
||||
typedef void (*telnet_parser_callback)(telnet_parser* parser, telnet_parser_event* event);
|
||||
|
||||
|
||||
telnet_parser* telnet_parser_new(void* userdata, telnet_parser_callback callback);
|
||||
void telnet_parser_free(telnet_parser* parser);
|
||||
|
||||
telnet_error telnet_parser_get_userdata(telnet_parser* parser, void** userdata);
|
||||
|
||||
telnet_error telnet_parser_parse(telnet_parser* parser,
|
||||
const telnet_byte* data,
|
||||
size_t length,
|
||||
size_t* bytes_used);
|
||||
|
||||
telnet_error telnet_parser_interrupt(telnet_parser* parser);
|
||||
|
||||
#endif // ANACHRONISM_PARSER_H
|
6
src/README.md
Normal file
6
src/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
* parser_common.rl
|
||||
<br>The language-agnostic Ragel grammar for the Telnet protocol.
|
||||
* parser.rl
|
||||
<br>The C implementation of the Ragel grammar. Compiled to parser.c by Ragel.
|
||||
* nvt.c
|
||||
<br>The core implementation of Anachronism's NVT and Channel constructs.
|
631
src/nvt.c
Normal file
631
src/nvt.c
Normal file
@ -0,0 +1,631 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <anachronism/nvt.h>
|
||||
#include <anachronism/parser.h>
|
||||
|
||||
|
||||
#define TELOPT_TOGGLE_CALLBACK(nvt, telopt, where_, status_) do { \
|
||||
if ((nvt)->telopt_callback) { \
|
||||
telnet_telopt_toggle_event ev; \
|
||||
ev.SUPER_.type = TELNET_EV_TELOPT_TOGGLE; \
|
||||
ev.where = (where_); \
|
||||
ev.status = (status_); \
|
||||
\
|
||||
(nvt)->telopt_callback((nvt), (telopt), (telnet_telopt_event*)&ev); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TELOPT_FOCUS_CALLBACK(nvt, telopt, status_) do { \
|
||||
if ((nvt)->telopt_callback) { \
|
||||
telnet_telopt_focus_event ev; \
|
||||
ev.SUPER_.type = TELNET_EV_TELOPT_FOCUS; \
|
||||
ev.status = (status_); \
|
||||
\
|
||||
(nvt)->telopt_callback((nvt), (telopt), (telnet_telopt_event*)&ev); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define TELOPT_DATA_CALLBACK(nvt, telopt, data_, length_) do { \
|
||||
if ((nvt)->telopt_callback) { \
|
||||
telnet_telopt_data_event ev; \
|
||||
ev.SUPER_.type = TELNET_EV_TELOPT_DATA; \
|
||||
ev.data = (data_); \
|
||||
ev.length = (length_); \
|
||||
\
|
||||
(nvt)->telopt_callback((nvt), (telopt), (telnet_telopt_event*)&ev); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SEND_CALLBACK(nvt, data_, length_) do { \
|
||||
if ((nvt)->callback) { \
|
||||
telnet_send_event ev; \
|
||||
ev.SUPER_.type = TELNET_EV_SEND; \
|
||||
ev.data = (data_); \
|
||||
ev.length = (length_); \
|
||||
\
|
||||
(nvt)->callback((nvt), (telnet_event*)&ev); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
// Q Method of Implementing TELNET Option Negotiation
|
||||
// ftp://ftp.rfc-editor.org/in-notes/rfc1143.txt
|
||||
typedef enum qstate {
|
||||
Q_NO = 0, Q_WANTYES, Q_WANTYESNO,
|
||||
Q_YES, Q_WANTNO, Q_WANTNOYES,
|
||||
} qstate;
|
||||
|
||||
typedef struct telnet_qstate
|
||||
{
|
||||
unsigned remote : 3;
|
||||
unsigned local : 3;
|
||||
} telnet_qstate;
|
||||
|
||||
struct telnet_nvt
|
||||
{
|
||||
telnet_parser* parser;
|
||||
telnet_qstate options[256]; // track the state of each subnegotiation option
|
||||
short current_remote;
|
||||
|
||||
telnet_nvt_event_callback callback;
|
||||
telnet_telopt_event_callback telopt_callback;
|
||||
telnet_negotiate_event_callback negotiate_callback;
|
||||
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
static unsigned char telopt_status(telnet_nvt* nvt,
|
||||
telnet_byte telopt,
|
||||
telnet_telopt_location where)
|
||||
{
|
||||
unsigned int qval = (where == TELNET_LOCAL) ?
|
||||
nvt->options[telopt].local :
|
||||
nvt->options[telopt].remote;
|
||||
|
||||
switch (qval) {
|
||||
case Q_YES: case Q_WANTNO: case Q_WANTNOYES:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#define telopt_subnegotiable(nvt, telopt) (telopt_status((nvt), (telopt), TELNET_REMOTE) || telopt_status((nvt), (telopt), TELNET_LOCAL))
|
||||
|
||||
|
||||
static void send_option(telnet_nvt* nvt, telnet_byte command, telnet_byte telopt)
|
||||
{
|
||||
const telnet_byte buf[] = {IAC_IAC, command, telopt};
|
||||
SEND_CALLBACK(nvt, buf, 3);
|
||||
}
|
||||
|
||||
static void process_option_event(telnet_nvt* nvt,
|
||||
telnet_byte command,
|
||||
telnet_byte telopt)
|
||||
{
|
||||
telnet_qstate* q = &nvt->options[telopt];
|
||||
// Every qstate begins zeroed-out, and Q_NO is 0.
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case IAC_WILL:
|
||||
switch (q->remote)
|
||||
{
|
||||
case Q_NO:
|
||||
if (nvt->negotiate_callback && nvt->negotiate_callback(nvt, telopt, TELNET_REMOTE)) {
|
||||
send_option(nvt, IAC_DO, telopt);
|
||||
q->remote = Q_YES;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1);
|
||||
} else {
|
||||
send_option(nvt, IAC_DONT, telopt);
|
||||
}
|
||||
break;
|
||||
case Q_WANTNO:
|
||||
// error
|
||||
q->remote = Q_NO;
|
||||
break;
|
||||
case Q_WANTNOYES:
|
||||
// error
|
||||
q->remote = Q_YES;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1);
|
||||
break;
|
||||
case Q_WANTYES:
|
||||
q->remote = Q_YES;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1);
|
||||
break;
|
||||
case Q_WANTYESNO:
|
||||
send_option(nvt, IAC_DONT, telopt);
|
||||
q->remote = Q_WANTNO;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case IAC_WONT:
|
||||
switch (q->remote)
|
||||
{
|
||||
case Q_YES:
|
||||
send_option(nvt, IAC_DONT, telopt);
|
||||
q->remote = Q_NO;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 0);
|
||||
break;
|
||||
case Q_WANTNO:
|
||||
q->remote = Q_NO;
|
||||
break;
|
||||
case Q_WANTNOYES:
|
||||
send_option(nvt, IAC_DO, telopt);
|
||||
q->remote = Q_WANTYES;
|
||||
break;
|
||||
case Q_WANTYES:
|
||||
q->remote = Q_NO;
|
||||
break;
|
||||
case Q_WANTYESNO:
|
||||
q->remote = Q_NO;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case IAC_DO:
|
||||
switch (q->local)
|
||||
{
|
||||
case Q_NO:
|
||||
if (nvt->negotiate_callback && nvt->negotiate_callback(nvt, telopt, TELNET_LOCAL)) {
|
||||
send_option(nvt, IAC_WILL, telopt);
|
||||
q->local = Q_YES;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1);
|
||||
} else {
|
||||
send_option(nvt, IAC_WONT, telopt);
|
||||
}
|
||||
break;
|
||||
case Q_WANTNO:
|
||||
// error
|
||||
q->local = Q_NO;
|
||||
break;
|
||||
case Q_WANTNOYES:
|
||||
// error
|
||||
q->local = Q_YES;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1);
|
||||
break;
|
||||
case Q_WANTYES:
|
||||
q->local = Q_YES;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1);
|
||||
break;
|
||||
case Q_WANTYESNO:
|
||||
send_option(nvt, IAC_WONT, telopt);
|
||||
q->local = Q_WANTNO;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case IAC_DONT:
|
||||
switch (q->local)
|
||||
{
|
||||
case Q_YES:
|
||||
send_option(nvt, IAC_DONT, telopt);
|
||||
q->local = Q_NO;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 0);
|
||||
break;
|
||||
case Q_WANTNO:
|
||||
q->local = Q_NO;
|
||||
break;
|
||||
case Q_WANTNOYES:
|
||||
send_option(nvt, IAC_WILL, telopt);
|
||||
q->local = Q_WANTYES;
|
||||
break;
|
||||
case Q_WANTYES:
|
||||
q->local = Q_NO;
|
||||
break;
|
||||
case Q_WANTYESNO:
|
||||
q->local = Q_NO;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void process_data_event(telnet_nvt* nvt,
|
||||
const telnet_byte* data,
|
||||
size_t length)
|
||||
{
|
||||
if (nvt->current_remote == -1) {
|
||||
// Main-line data
|
||||
if (nvt->callback) {
|
||||
telnet_data_event ev;
|
||||
ev.SUPER_.type = TELNET_EV_DATA;
|
||||
ev.data = data;
|
||||
ev.length = length;
|
||||
nvt->callback(nvt, (telnet_event*)&ev);
|
||||
}
|
||||
} else {
|
||||
// Telopt data
|
||||
telnet_byte telopt = (telnet_byte)nvt->current_remote;
|
||||
|
||||
if (nvt->telopt_callback) {
|
||||
// Make sure the telopt is enabled
|
||||
if (telopt_subnegotiable(nvt, telopt)) {
|
||||
telnet_telopt_data_event ev;
|
||||
ev.SUPER_.type = TELNET_EV_TELOPT_DATA;
|
||||
ev.data = data;
|
||||
ev.length = length;
|
||||
nvt->telopt_callback(nvt, telopt, (telnet_telopt_event*)&ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void process_subnegotiation_event(telnet_nvt* nvt,
|
||||
int open,
|
||||
telnet_byte telopt)
|
||||
{
|
||||
if (open) {
|
||||
nvt->current_remote = telopt;
|
||||
} else {
|
||||
nvt->current_remote = -1;
|
||||
}
|
||||
|
||||
if (nvt->telopt_callback) {
|
||||
// Make sure the telopt is enabled
|
||||
if (telopt_subnegotiable(nvt, telopt)) {
|
||||
telnet_telopt_focus_event ev;
|
||||
ev.SUPER_.type = TELNET_EV_TELOPT_FOCUS;
|
||||
ev.focus = open;
|
||||
nvt->telopt_callback(nvt, telopt, (telnet_telopt_event*)&ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void process_event(telnet_parser* parser, telnet_parser_event* event)
|
||||
{
|
||||
telnet_nvt* nvt = NULL;
|
||||
telnet_parser_get_userdata(parser, (void*)&nvt);
|
||||
|
||||
switch (event->type)
|
||||
{
|
||||
case TELNET_EV_PARSER_DATA:
|
||||
{
|
||||
telnet_parser_data_event* ev = (telnet_parser_data_event*)event;
|
||||
process_data_event(nvt, ev->data, ev->length);
|
||||
break;
|
||||
}
|
||||
|
||||
case TELNET_EV_PARSER_OPTION:
|
||||
{
|
||||
telnet_parser_option_event* ev = (telnet_parser_option_event*)event;
|
||||
process_option_event(nvt, ev->command, ev->option);
|
||||
break;
|
||||
}
|
||||
|
||||
case TELNET_EV_PARSER_SUBNEGOTIATION:
|
||||
{
|
||||
telnet_parser_subnegotiation_event* ev = (telnet_parser_subnegotiation_event*)event;
|
||||
process_subnegotiation_event(nvt, ev->active, ev->option);
|
||||
break;
|
||||
}
|
||||
|
||||
case TELNET_EV_PARSER_COMMAND:
|
||||
{
|
||||
if (nvt->callback) {
|
||||
telnet_parser_command_event* parser_ev = (telnet_parser_command_event*) event;
|
||||
|
||||
telnet_command_event ev;
|
||||
ev.SUPER_.type = TELNET_EV_COMMAND;
|
||||
ev.command = parser_ev->command;
|
||||
nvt->callback(nvt, (telnet_event*)&ev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TELNET_EV_PARSER_WARNING:
|
||||
{
|
||||
if (nvt->callback) {
|
||||
telnet_parser_warning_event* parser_ev = (telnet_parser_warning_event*) event;
|
||||
|
||||
telnet_warning_event ev;
|
||||
ev.SUPER_.type = TELNET_EV_WARNING;
|
||||
ev.message = parser_ev->message;
|
||||
ev.position = parser_ev->position;
|
||||
nvt->callback(nvt, (telnet_event*)&ev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
telnet_nvt* telnet_nvt_new(void* userdata,
|
||||
telnet_nvt_event_callback nvt_callback,
|
||||
telnet_telopt_event_callback telopt_callback,
|
||||
telnet_negotiate_event_callback negotiate_callback)
|
||||
{
|
||||
telnet_nvt* nvt = malloc(sizeof(telnet_nvt));
|
||||
if (nvt)
|
||||
{
|
||||
telnet_parser* parser = telnet_parser_new((void*)nvt, &process_event);
|
||||
if (parser)
|
||||
{
|
||||
memset(nvt, 0, sizeof(*nvt));
|
||||
nvt->parser = parser;
|
||||
nvt->callback = nvt_callback;
|
||||
nvt->telopt_callback = telopt_callback;
|
||||
nvt->negotiate_callback = negotiate_callback;
|
||||
nvt->userdata = userdata;
|
||||
nvt->current_remote = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
free(nvt);
|
||||
nvt = NULL;
|
||||
}
|
||||
}
|
||||
return nvt;
|
||||
}
|
||||
|
||||
void telnet_nvt_free(telnet_nvt* nvt)
|
||||
{
|
||||
if (nvt)
|
||||
{
|
||||
telnet_parser_free(nvt->parser);
|
||||
free(nvt);
|
||||
}
|
||||
}
|
||||
|
||||
telnet_error telnet_get_userdata(telnet_nvt* nvt, void** userdata)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
|
||||
*userdata = nvt->userdata;
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_receive(telnet_nvt* nvt, const telnet_byte* data, size_t length, size_t* bytes_used)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
|
||||
return telnet_parser_parse(nvt->parser, data, length, bytes_used);
|
||||
}
|
||||
|
||||
telnet_error telnet_interrupt(telnet_nvt* nvt)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
|
||||
return telnet_parser_interrupt(nvt->parser);
|
||||
}
|
||||
|
||||
|
||||
static int safe_concat(const telnet_byte* in, size_t inlen, telnet_byte* out, size_t outlen)
|
||||
{
|
||||
// Copy as much as possible into the buffer.
|
||||
memcpy(out, in, (outlen < inlen) ? outlen : inlen);
|
||||
|
||||
// true if everything could be copied, false otherwise
|
||||
return outlen >= inlen;
|
||||
}
|
||||
|
||||
// Escapes any special characters in data, writing the result data to out.
|
||||
// Returns -1 if not everything could be copied (and out is full).
|
||||
// Otherwise returns the length of the data in out.
|
||||
//
|
||||
// To avoid potential -1 return values, pass in an out buffer double the length of the data buffer.
|
||||
static size_t telnet_escape(const telnet_byte* data, size_t length, telnet_byte* out, size_t outsize)
|
||||
{
|
||||
if (data == NULL || out == NULL)
|
||||
return 0;
|
||||
|
||||
size_t outlen = 0;
|
||||
size_t left = 0;
|
||||
size_t right = 0;
|
||||
const char* seq = NULL;
|
||||
for (; right < length; ++right)
|
||||
{
|
||||
switch (data[right])
|
||||
{
|
||||
case IAC_IAC:
|
||||
seq = "\xFF\xFF";
|
||||
break;
|
||||
case '\r':
|
||||
// Only escape \r if it doesn't immediately precede \n.
|
||||
if (right + 1 >= length || data[right+1] != '\n')
|
||||
{
|
||||
seq = "\r\0";
|
||||
break;
|
||||
}
|
||||
// !!FALLTHROUGH!!
|
||||
default:
|
||||
continue; // Move to the next character
|
||||
}
|
||||
|
||||
// Add any normal data that hasn't been added yet.
|
||||
if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0)
|
||||
return -1;
|
||||
outlen += right - left;
|
||||
left = right + 1;
|
||||
|
||||
// Add the escape sequence.
|
||||
if (safe_concat((const telnet_byte*)seq, 2, out+outlen, outsize-outlen) == 0)
|
||||
return -1;
|
||||
outlen += 2;
|
||||
}
|
||||
|
||||
// Add any leftover normal data.
|
||||
if (left < right)
|
||||
{
|
||||
if (safe_concat(data+left, right-left, out+outlen, outsize-outlen) == 0)
|
||||
return -1;
|
||||
outlen += right - left;
|
||||
}
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
telnet_error telnet_send_data(telnet_nvt* nvt, const telnet_byte* data, const size_t length)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
else if (!nvt->callback)
|
||||
return TELNET_E_OK; // immediate success since they apparently don't want the data to go anywhere
|
||||
|
||||
// Due to the nature of the protocol, the most any one byte can be encoded as is two bytes.
|
||||
// Hence, the smallest buffer guaranteed to contain any input is double the length of the source.
|
||||
size_t bufsize = sizeof(telnet_byte) * length * 2;
|
||||
telnet_byte* buf = malloc(bufsize);
|
||||
if (!buf)
|
||||
return TELNET_E_ALLOC;
|
||||
|
||||
bufsize = telnet_escape(data, length, buf, bufsize);
|
||||
|
||||
SEND_CALLBACK(nvt, buf, bufsize);
|
||||
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_send_command(telnet_nvt* nvt, const telnet_byte command)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
else if (command >= IAC_SB || command == IAC_SE)
|
||||
return TELNET_E_INVALID_COMMAND; // Invalid command
|
||||
|
||||
const telnet_byte buf[] = {IAC_IAC, command};
|
||||
SEND_CALLBACK(nvt, buf, 2);
|
||||
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_send_subnegotiation(telnet_nvt* nvt, const telnet_byte option, const telnet_byte* data, const size_t length)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
else if (!telopt_subnegotiable(nvt, option))
|
||||
return TELNET_E_NOT_SUBNEGOTIABLE;
|
||||
else if (!nvt->callback)
|
||||
return TELNET_E_OK;
|
||||
|
||||
// length*2 is the maximum buffer size needed for an escaped string.
|
||||
// The extra five bytes are for the IAC, SB, <option>, IAC, and SE frame around the data.
|
||||
size_t bufsize = (sizeof(telnet_byte) * length * 2) + 5;
|
||||
telnet_byte* buf = malloc(bufsize);
|
||||
if (!buf)
|
||||
return TELNET_E_ALLOC;
|
||||
|
||||
// Begin with IAC SB <option>
|
||||
telnet_byte iac[] = {IAC_IAC, IAC_SB, option};
|
||||
memcpy(buf, iac, 3);
|
||||
|
||||
// Add the subnegotiation body
|
||||
size_t escaped_length = telnet_escape(data, length, buf+3, bufsize-3) + 3;
|
||||
|
||||
// End with IAC SE
|
||||
iac[1] = IAC_SE;
|
||||
memcpy(buf+escaped_length, iac, 2);
|
||||
escaped_length += 2;
|
||||
|
||||
SEND_CALLBACK(nvt, buf, escaped_length);
|
||||
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
|
||||
telnet_error telnet_telopt_enable(telnet_nvt* nvt,
|
||||
telnet_byte telopt,
|
||||
telnet_telopt_location where)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
|
||||
telnet_qstate* q = &nvt->options[telopt];
|
||||
if (where == TELNET_LOCAL) {
|
||||
switch (q->local)
|
||||
{
|
||||
case Q_NO:
|
||||
q->local = Q_WANTYES;
|
||||
send_option(nvt, IAC_WILL, telopt);
|
||||
break;
|
||||
case Q_WANTNO:
|
||||
q->local = Q_WANTNOYES;
|
||||
break;
|
||||
case Q_WANTYESNO:
|
||||
q->local = Q_WANTYES;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (q->remote)
|
||||
{
|
||||
case Q_NO:
|
||||
q->remote = Q_WANTYES;
|
||||
send_option(nvt, IAC_DO, telopt);
|
||||
break;
|
||||
case Q_WANTNO:
|
||||
q->remote = Q_WANTNOYES;
|
||||
break;
|
||||
case Q_WANTYESNO:
|
||||
q->remote = Q_WANTYES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_telopt_disable(telnet_nvt* nvt,
|
||||
telnet_byte telopt,
|
||||
telnet_telopt_location where)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
|
||||
telnet_qstate* q = &nvt->options[telopt];
|
||||
if (where == TELNET_LOCAL) {
|
||||
switch (q->local)
|
||||
{
|
||||
case Q_YES:
|
||||
send_option(nvt, IAC_WONT, telopt);
|
||||
q->local = Q_WANTNO;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_LOCAL, 0);
|
||||
break;
|
||||
case Q_WANTNOYES:
|
||||
q->local = Q_WANTNO;
|
||||
break;
|
||||
case Q_WANTYES:
|
||||
q->local = Q_WANTYESNO;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (q->remote)
|
||||
{
|
||||
case Q_YES:
|
||||
send_option(nvt, IAC_DONT, telopt);
|
||||
q->remote = Q_WANTNO;
|
||||
TELOPT_TOGGLE_CALLBACK(nvt, telopt, TELNET_REMOTE, 0);
|
||||
break;
|
||||
case Q_WANTNOYES:
|
||||
q->remote = Q_WANTNO;
|
||||
break;
|
||||
case Q_WANTYES:
|
||||
q->remote = Q_WANTYESNO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_telopt_status(telnet_nvt* nvt,
|
||||
telnet_byte telopt,
|
||||
telnet_telopt_location where,
|
||||
unsigned char* status)
|
||||
{
|
||||
if (!nvt)
|
||||
return TELNET_E_BAD_NVT;
|
||||
|
||||
*status = telopt_status(nvt, telopt, where);
|
||||
return TELNET_E_OK;
|
||||
}
|
459
src/parser.c
Normal file
459
src/parser.c
Normal file
@ -0,0 +1,459 @@
|
||||
|
||||
#line 1 "src/parser.rl"
|
||||
#include <string.h>
|
||||
#include <anachronism/parser.h>
|
||||
|
||||
#define BASE_EV(ev, t) \
|
||||
(ev).SUPER_.type = TELNET_EV_PARSER_##t
|
||||
|
||||
#define EV_DATA(ev, text, len) do {\
|
||||
BASE_EV(ev, DATA);\
|
||||
(ev).data = (text);\
|
||||
(ev).length = (len);\
|
||||
} while (0)
|
||||
|
||||
#define EV_COMMAND(ev, cmd) do {\
|
||||
BASE_EV(ev, COMMAND);\
|
||||
(ev).command = (cmd);\
|
||||
} while (0)
|
||||
|
||||
#define EV_OPTION(ev, cmd, opt) do {\
|
||||
BASE_EV(ev, OPTION);\
|
||||
(ev).command = (cmd);\
|
||||
(ev).option = (opt);\
|
||||
} while (0)
|
||||
|
||||
#define EV_SUBNEGOTIATION(ev, act, opt) do {\
|
||||
BASE_EV(ev, SUBNEGOTIATION);\
|
||||
(ev).active = (act);\
|
||||
(ev).option = (opt);\
|
||||
} while (0)
|
||||
|
||||
#define EV_WARNING(ev, msg, pos) do {\
|
||||
BASE_EV(ev, WARNING);\
|
||||
(ev).message = (msg);\
|
||||
(ev).position = (pos);\
|
||||
} while (0)
|
||||
|
||||
struct telnet_parser {
|
||||
int cs; /* current Ragel state */
|
||||
const telnet_byte* p; /* current position */
|
||||
const telnet_byte* pe; /* end of current packet */
|
||||
const telnet_byte* eof; /* end-of-file marker */
|
||||
|
||||
telnet_byte option_mark; /* temporary storage for a command byte */
|
||||
unsigned char interrupted; /* Flag for interrupts */
|
||||
|
||||
telnet_parser_callback callback; /* Receiver of Telnet events*/
|
||||
void* userdata; /* Context for parser callback */
|
||||
};
|
||||
|
||||
|
||||
#line 53 "src/parser.c"
|
||||
static const int telnet_parser_start = 7;
|
||||
static const int telnet_parser_first_final = 7;
|
||||
static const int telnet_parser_error = -1;
|
||||
|
||||
static const int telnet_parser_en_main = 7;
|
||||
|
||||
|
||||
#line 130 "src/parser.rl"
|
||||
|
||||
|
||||
telnet_parser* telnet_parser_new(void* userdata,
|
||||
telnet_parser_callback callback)
|
||||
{
|
||||
telnet_parser* parser = malloc(sizeof(telnet_parser));
|
||||
if (parser)
|
||||
{
|
||||
memset(parser, 0, sizeof(*parser));
|
||||
|
||||
#line 72 "src/parser.c"
|
||||
{
|
||||
parser->cs = telnet_parser_start;
|
||||
}
|
||||
|
||||
#line 140 "src/parser.rl"
|
||||
parser->callback = callback;
|
||||
parser->userdata = userdata;
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
void telnet_parser_free(telnet_parser* parser)
|
||||
{
|
||||
free(parser);
|
||||
}
|
||||
|
||||
telnet_error telnet_parser_get_userdata(telnet_parser* parser, void** userdata)
|
||||
{
|
||||
if (!parser)
|
||||
return TELNET_E_BAD_PARSER;
|
||||
|
||||
*userdata = parser->userdata;
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_parser_parse(telnet_parser* parser,
|
||||
const telnet_byte* data,
|
||||
size_t length,
|
||||
size_t* bytes_used)
|
||||
{
|
||||
if (!parser)
|
||||
return TELNET_E_BAD_PARSER;
|
||||
|
||||
// Reset the interrupt flag
|
||||
parser->interrupted = 0;
|
||||
|
||||
// Only bother saving text if it'll be used
|
||||
telnet_byte* buf = NULL;
|
||||
size_t buflen = 0;
|
||||
if (parser->callback)
|
||||
{
|
||||
// Because of how the parser translates data, a run of text is guaranteed to
|
||||
// be at most 'length' characters long. In practice it's usually less, due to
|
||||
// escaped characters (IAC IAC -> IAC) and text separated by commands.
|
||||
buf = malloc(length * sizeof(*buf));
|
||||
if (!buf)
|
||||
return TELNET_E_ALLOC;
|
||||
}
|
||||
|
||||
parser->p = data;
|
||||
parser->pe = data + length;
|
||||
parser->eof = parser->pe;
|
||||
|
||||
|
||||
#line 127 "src/parser.c"
|
||||
{
|
||||
if ( ( parser->p) == ( parser->pe) )
|
||||
goto _test_eof;
|
||||
switch ( parser->cs )
|
||||
{
|
||||
tr1:
|
||||
#line 6 "src/parser_common.rl"
|
||||
{( parser->p)--;}
|
||||
#line 111 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_warning_event ev;
|
||||
EV_WARNING(ev, "Invalid \\r: not followed by \\n or \\0.", ( parser->p)-data);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
goto st7;
|
||||
tr2:
|
||||
#line 67 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf)
|
||||
buf[buflen++] = (*( parser->p));
|
||||
}
|
||||
goto st7;
|
||||
tr3:
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
#line 72 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf)
|
||||
{
|
||||
telnet_parser_command_event ev;
|
||||
EV_COMMAND(ev, (*( parser->p)));
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
goto st7;
|
||||
tr12:
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
#line 6 "src/parser_common.rl"
|
||||
{( parser->p)--;}
|
||||
#line 119 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_warning_event ev;
|
||||
EV_WARNING(ev, "IAC followed by invalid command.", ( parser->p)-data);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
#line 102 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_subnegotiation_event ev;
|
||||
EV_SUBNEGOTIATION(ev, 0, parser->option_mark);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
goto st7;
|
||||
tr13:
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
#line 102 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_subnegotiation_event ev;
|
||||
EV_SUBNEGOTIATION(ev, 0, parser->option_mark);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
goto st7;
|
||||
tr14:
|
||||
#line 84 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf)
|
||||
{
|
||||
telnet_parser_option_event ev;
|
||||
EV_OPTION(ev, parser->option_mark, (*( parser->p)));
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
goto st7;
|
||||
st7:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof7;
|
||||
case 7:
|
||||
#line 252 "src/parser.c"
|
||||
switch( (*( parser->p)) ) {
|
||||
case 13u: goto tr15;
|
||||
case 255u: goto st1;
|
||||
}
|
||||
goto tr2;
|
||||
tr15:
|
||||
#line 67 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf)
|
||||
buf[buflen++] = (*( parser->p));
|
||||
}
|
||||
goto st0;
|
||||
st0:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof0;
|
||||
case 0:
|
||||
#line 269 "src/parser.c"
|
||||
switch( (*( parser->p)) ) {
|
||||
case 0u: goto st7;
|
||||
case 10u: goto tr2;
|
||||
}
|
||||
goto tr1;
|
||||
st1:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof1;
|
||||
case 1:
|
||||
switch( (*( parser->p)) ) {
|
||||
case 250u: goto tr4;
|
||||
case 255u: goto tr2;
|
||||
}
|
||||
if ( 251u <= (*( parser->p)) && (*( parser->p)) <= 254u )
|
||||
goto tr5;
|
||||
goto tr3;
|
||||
tr4:
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
goto st2;
|
||||
st2:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof2;
|
||||
case 2:
|
||||
#line 302 "src/parser.c"
|
||||
goto tr6;
|
||||
tr11:
|
||||
#line 6 "src/parser_common.rl"
|
||||
{( parser->p)--;}
|
||||
#line 111 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_warning_event ev;
|
||||
EV_WARNING(ev, "Invalid \\r: not followed by \\n or \\0.", ( parser->p)-data);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
goto st3;
|
||||
tr7:
|
||||
#line 67 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf)
|
||||
buf[buflen++] = (*( parser->p));
|
||||
}
|
||||
goto st3;
|
||||
tr6:
|
||||
#line 93 "src/parser.rl"
|
||||
{
|
||||
parser->option_mark = (*( parser->p));
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_subnegotiation_event ev;
|
||||
EV_SUBNEGOTIATION(ev, 1, parser->option_mark);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
goto st3;
|
||||
st3:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof3;
|
||||
case 3:
|
||||
#line 350 "src/parser.c"
|
||||
switch( (*( parser->p)) ) {
|
||||
case 13u: goto tr8;
|
||||
case 255u: goto st5;
|
||||
}
|
||||
goto tr7;
|
||||
tr8:
|
||||
#line 67 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buf)
|
||||
buf[buflen++] = (*( parser->p));
|
||||
}
|
||||
goto st4;
|
||||
st4:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof4;
|
||||
case 4:
|
||||
#line 367 "src/parser.c"
|
||||
switch( (*( parser->p)) ) {
|
||||
case 0u: goto st3;
|
||||
case 10u: goto tr7;
|
||||
}
|
||||
goto tr11;
|
||||
st5:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof5;
|
||||
case 5:
|
||||
switch( (*( parser->p)) ) {
|
||||
case 240u: goto tr13;
|
||||
case 255u: goto tr7;
|
||||
}
|
||||
goto tr12;
|
||||
tr5:
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
#line 81 "src/parser.rl"
|
||||
{
|
||||
parser->option_mark = (*( parser->p));
|
||||
}
|
||||
goto st6;
|
||||
st6:
|
||||
if ( ++( parser->p) == ( parser->pe) )
|
||||
goto _test_eof6;
|
||||
case 6:
|
||||
#line 402 "src/parser.c"
|
||||
goto tr14;
|
||||
}
|
||||
_test_eof7: parser->cs = 7; goto _test_eof;
|
||||
_test_eof0: parser->cs = 0; goto _test_eof;
|
||||
_test_eof1: parser->cs = 1; goto _test_eof;
|
||||
_test_eof2: parser->cs = 2; goto _test_eof;
|
||||
_test_eof3: parser->cs = 3; goto _test_eof;
|
||||
_test_eof4: parser->cs = 4; goto _test_eof;
|
||||
_test_eof5: parser->cs = 5; goto _test_eof;
|
||||
_test_eof6: parser->cs = 6; goto _test_eof;
|
||||
|
||||
_test_eof: {}
|
||||
if ( ( parser->p) == ( parser->eof) )
|
||||
{
|
||||
switch ( parser->cs ) {
|
||||
case 3:
|
||||
case 7:
|
||||
#line 57 "src/parser.rl"
|
||||
{
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#line 431 "src/parser.c"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#line 189 "src/parser.rl"
|
||||
|
||||
if (bytes_used != NULL)
|
||||
*bytes_used = parser->p - data;
|
||||
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
parser->p = parser->pe = parser->eof = NULL;
|
||||
|
||||
return (parser->interrupted) ? TELNET_E_INTERRUPT : TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_parser_interrupt(telnet_parser* parser)
|
||||
{
|
||||
if (!parser)
|
||||
return TELNET_E_BAD_PARSER;
|
||||
|
||||
// Force the parser to stop where it's at.
|
||||
if (parser->p)
|
||||
parser->eof = parser->pe = parser->p + 1;
|
||||
|
||||
parser->interrupted = 1;
|
||||
return TELNET_E_OK;
|
||||
}
|
211
src/parser.rl
Normal file
211
src/parser.rl
Normal file
@ -0,0 +1,211 @@
|
||||
#include <string.h>
|
||||
#include <anachronism/parser.h>
|
||||
|
||||
#define BASE_EV(ev, t) \
|
||||
(ev).SUPER_.type = TELNET_EV_PARSER_##t
|
||||
|
||||
#define EV_DATA(ev, text, len) do {\
|
||||
BASE_EV(ev, DATA);\
|
||||
(ev).data = (text);\
|
||||
(ev).length = (len);\
|
||||
} while (0)
|
||||
|
||||
#define EV_COMMAND(ev, cmd) do {\
|
||||
BASE_EV(ev, COMMAND);\
|
||||
(ev).command = (cmd);\
|
||||
} while (0)
|
||||
|
||||
#define EV_OPTION(ev, cmd, opt) do {\
|
||||
BASE_EV(ev, OPTION);\
|
||||
(ev).command = (cmd);\
|
||||
(ev).option = (opt);\
|
||||
} while (0)
|
||||
|
||||
#define EV_SUBNEGOTIATION(ev, act, opt) do {\
|
||||
BASE_EV(ev, SUBNEGOTIATION);\
|
||||
(ev).active = (act);\
|
||||
(ev).option = (opt);\
|
||||
} while (0)
|
||||
|
||||
#define EV_WARNING(ev, msg, pos) do {\
|
||||
BASE_EV(ev, WARNING);\
|
||||
(ev).message = (msg);\
|
||||
(ev).position = (pos);\
|
||||
} while (0)
|
||||
|
||||
struct telnet_parser {
|
||||
int cs; /* current Ragel state */
|
||||
const telnet_byte* p; /* current position */
|
||||
const telnet_byte* pe; /* end of current packet */
|
||||
const telnet_byte* eof; /* end-of-file marker */
|
||||
|
||||
telnet_byte option_mark; /* temporary storage for a command byte */
|
||||
unsigned char interrupted; /* Flag for interrupts */
|
||||
|
||||
telnet_parser_callback callback; /* Receiver of Telnet events*/
|
||||
void* userdata; /* Context for parser callback */
|
||||
};
|
||||
|
||||
%%{
|
||||
machine telnet_parser;
|
||||
|
||||
access parser->;
|
||||
variable p parser->p;
|
||||
variable pe parser->pe;
|
||||
variable eof parser->eof;
|
||||
|
||||
action flush_text {
|
||||
if (parser->callback && buflen > 0)
|
||||
{
|
||||
telnet_parser_data_event ev;
|
||||
EV_DATA(ev, buf, buflen);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
buflen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
action char {
|
||||
if (parser->callback && buf)
|
||||
buf[buflen++] = fc;
|
||||
}
|
||||
|
||||
action basic_command {
|
||||
if (parser->callback && buf)
|
||||
{
|
||||
telnet_parser_command_event ev;
|
||||
EV_COMMAND(ev, fc);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
|
||||
action option_mark {
|
||||
parser->option_mark = fc;
|
||||
}
|
||||
action option_command {
|
||||
if (parser->callback && buf)
|
||||
{
|
||||
telnet_parser_option_event ev;
|
||||
EV_OPTION(ev, parser->option_mark, fc);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
|
||||
action subneg_command {
|
||||
parser->option_mark = fc;
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_subnegotiation_event ev;
|
||||
EV_SUBNEGOTIATION(ev, 1, parser->option_mark);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
action subneg_command_end {
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_subnegotiation_event ev;
|
||||
EV_SUBNEGOTIATION(ev, 0, parser->option_mark);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
|
||||
action warning_cr {
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_warning_event ev;
|
||||
EV_WARNING(ev, "Invalid \\r: not followed by \\n or \\0.", fpc-data);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
action warning_iac {
|
||||
if (parser->callback && buf != NULL)
|
||||
{
|
||||
telnet_parser_warning_event ev;
|
||||
EV_WARNING(ev, "IAC followed by invalid command.", fpc-data);
|
||||
parser->callback(parser, (telnet_parser_event*)&ev);
|
||||
}
|
||||
}
|
||||
|
||||
include telnet_parser_common "parser_common.rl";
|
||||
write data;
|
||||
}%%
|
||||
|
||||
telnet_parser* telnet_parser_new(void* userdata,
|
||||
telnet_parser_callback callback)
|
||||
{
|
||||
telnet_parser* parser = malloc(sizeof(telnet_parser));
|
||||
if (parser)
|
||||
{
|
||||
memset(parser, 0, sizeof(*parser));
|
||||
%% write init;
|
||||
parser->callback = callback;
|
||||
parser->userdata = userdata;
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
void telnet_parser_free(telnet_parser* parser)
|
||||
{
|
||||
free(parser);
|
||||
}
|
||||
|
||||
telnet_error telnet_parser_get_userdata(telnet_parser* parser, void** userdata)
|
||||
{
|
||||
if (!parser)
|
||||
return TELNET_E_BAD_PARSER;
|
||||
|
||||
*userdata = parser->userdata;
|
||||
return TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_parser_parse(telnet_parser* parser,
|
||||
const telnet_byte* data,
|
||||
size_t length,
|
||||
size_t* bytes_used)
|
||||
{
|
||||
if (!parser)
|
||||
return TELNET_E_BAD_PARSER;
|
||||
|
||||
// Reset the interrupt flag
|
||||
parser->interrupted = 0;
|
||||
|
||||
// Only bother saving text if it'll be used
|
||||
telnet_byte* buf = NULL;
|
||||
size_t buflen = 0;
|
||||
if (parser->callback)
|
||||
{
|
||||
// Because of how the parser translates data, a run of text is guaranteed to
|
||||
// be at most 'length' characters long. In practice it's usually less, due to
|
||||
// escaped characters (IAC IAC -> IAC) and text separated by commands.
|
||||
buf = malloc(length * sizeof(*buf));
|
||||
if (!buf)
|
||||
return TELNET_E_ALLOC;
|
||||
}
|
||||
|
||||
parser->p = data;
|
||||
parser->pe = data + length;
|
||||
parser->eof = parser->pe;
|
||||
|
||||
%% write exec;
|
||||
|
||||
if (bytes_used != NULL)
|
||||
*bytes_used = parser->p - data;
|
||||
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
parser->p = parser->pe = parser->eof = NULL;
|
||||
|
||||
return (parser->interrupted) ? TELNET_E_INTERRUPT : TELNET_E_OK;
|
||||
}
|
||||
|
||||
telnet_error telnet_parser_interrupt(telnet_parser* parser)
|
||||
{
|
||||
if (!parser)
|
||||
return TELNET_E_BAD_PARSER;
|
||||
|
||||
// Force the parser to stop where it's at.
|
||||
if (parser->p)
|
||||
parser->eof = parser->pe = parser->p + 1;
|
||||
|
||||
parser->interrupted = 1;
|
||||
return TELNET_E_OK;
|
||||
}
|
85
src/parser_common.rl
Normal file
85
src/parser_common.rl
Normal file
@ -0,0 +1,85 @@
|
||||
%%{
|
||||
machine telnet_parser_common;
|
||||
alphtype unsigned char;
|
||||
|
||||
# Shorthand for tidiness.
|
||||
action fhold {fhold;}
|
||||
|
||||
# Special bytes that must be handled differently from normal text:
|
||||
CR = "\r"; # Only \0 or \n may follow
|
||||
IAC = 255; # Telnet command marker
|
||||
special_byte = CR | IAC;
|
||||
|
||||
# The only bytes that may follow a CR:
|
||||
NL = "\n";
|
||||
NUL = "\0";
|
||||
|
||||
# The only bytes that may follow an IAC:
|
||||
SE = 240;
|
||||
NOP = 241;
|
||||
DM = 242;
|
||||
BRK = 243;
|
||||
IP = 244;
|
||||
AO = 245;
|
||||
AYT = 246;
|
||||
EC = 247;
|
||||
EL = 248;
|
||||
GA = 249;
|
||||
SB = 250;
|
||||
WILL = 251;
|
||||
WONT = 252;
|
||||
DO = 253;
|
||||
DONT = 254;
|
||||
# IAC IAC is interpreted as a plain-text IAC byte.
|
||||
|
||||
# Sorting the above IAC commands by type:
|
||||
iac_option_type = WILL | WONT | DO | DONT;
|
||||
iac_subneg_type = SB;
|
||||
iac_command_type = ^(iac_option_type | iac_subneg_type | IAC);
|
||||
|
||||
###
|
||||
# Plain text
|
||||
###
|
||||
plain_text = (^special_byte) @char;
|
||||
cr_seq = CR @char
|
||||
( NUL
|
||||
| NL @char
|
||||
| ^(NUL|NL) @fhold @warning_cr @flush_text
|
||||
);
|
||||
|
||||
###
|
||||
# IAC sequence
|
||||
###
|
||||
iac_command = iac_command_type @basic_command;
|
||||
|
||||
iac_option = iac_option_type @option_mark
|
||||
any @option_command;
|
||||
|
||||
iac_subneg = iac_subneg_type any @subneg_command
|
||||
( plain_text
|
||||
| cr_seq
|
||||
| IAC IAC @char
|
||||
)** %/flush_text
|
||||
IAC
|
||||
( SE
|
||||
| ^(IAC|SE) @fhold @warning_iac
|
||||
) >flush_text @subneg_command_end;
|
||||
|
||||
iac_seq = ( iac_command
|
||||
| iac_option
|
||||
| iac_subneg
|
||||
);
|
||||
|
||||
###
|
||||
# Telnet stream
|
||||
###
|
||||
telnet_stream = ( plain_text
|
||||
| cr_seq
|
||||
| IAC
|
||||
( IAC @char
|
||||
| iac_seq >flush_text
|
||||
)
|
||||
)** %/flush_text;
|
||||
|
||||
main := telnet_stream;
|
||||
}%%
|
Loading…
Reference in New Issue
Block a user