Squashed 'outside/anachronism/' content from commit 02fa3b0

git-subtree-dir: outside/anachronism
git-subtree-split: 02fa3b064112fa1d75209f123c07b5c7d58329b7
This commit is contained in:
~hatteb-mitlyd 2014-06-07 13:59:16 -07:00
commit 7536d1406f
14 changed files with 1996 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

19
LICENSE Normal file
View 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
View 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
View 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
View 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 &lt;id> or IAC DO &lt;id>. The remote host
then responds with IAC DO &lt;id> or IAC WILL &lt;id>, respectively. Alternatively,
the request may be denied using IAC DONT &lt;id> or IAC WONT &lt;id>, respectively.
In order to switch to a specific channel, the IAC SB &lt;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

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

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

View 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
View 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
View 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
View 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
View 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
View 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;
}%%