libtelnet/README

483 lines
19 KiB
Plaintext
Raw Normal View History

2009-03-13 03:33:27 +00:00
=====================================================================
libtelnet - TELNET protocol handling library
=====================================================================
2009-03-14 17:39:07 +00:00
http://github.com/elanthis/libtelnet
Sean Middleditch
sean@sourcemud.org
2009-03-13 03:33:27 +00:00
---------------------------------------------------------------------
The author or authors of this code dedicate any and all copyright
interest in this code to the public domain. We make this dedication
for the benefit of the public at large and to the detriment of our
heirs and successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
code under copyright law.
---------------------------------------------------------------------
2009-03-14 09:24:56 +00:00
*** TODO ***
- automatic MCCP2 handling (controllable by host app)
? ZMP parsing
? MSSP parsing
? ENVIRON/NEW-ENVIRON parsing
? telnet-status testing tool
2009-03-13 03:33:27 +00:00
I. INTRODUCTION
=====================================================================
libtelnet provides safe and correct handling of the core TELNET
2009-03-16 05:06:27 +00:00
protocol. In addition to the base TELNET protocol, libtelnet also
implements the Q method of TELNET option negotiation. libtelnet
can be used for writing servers, clients, or proxies.
2009-03-13 03:33:27 +00:00
2009-03-14 17:39:07 +00:00
For more information on the TELNET protocol, see:
http://www.faqs.org/rfcs/rfc854.html
2009-03-16 05:06:27 +00:00
http://www.faqs.org/rfcs/rfc1143.html
2009-03-14 17:39:07 +00:00
2009-03-13 03:33:27 +00:00
II. LIBTELNET API
=====================================================================
2009-03-14 17:39:07 +00:00
The libtelnet API contains several distinct parts. The first part is
the basic initialization and deinitialization routines. The second
part is a single function for pushing received data into the
libtelnet processor. The third part is the libtelnet_send_*()
functions, which generate TELNET commands and ensure data is properly
formatted before sending over the wire. The final part is the event
handler interface.
2009-03-14 17:39:07 +00:00
IIa. Initialization
struct libtelnet_t;
This structure represents the state of the TELNET protocol for a
single connection. Each connection utilizing TELNET must have
its own libtelnet_t structure, which is passed to all libtelnet
API calls.
void libtelnet_init(libtelnet_t *telnet, libtelnet_event_handler_t handler,
unsigned char flags, void *user_data);
2009-03-14 17:39:07 +00:00
The libtelnet_init() function is responsible for initializing
the data in a libtelnet_t structure. It must be called
immediately after establishing a connection and before any other
libtelnet API calls are made.
The handler parameter must be a function matching the
libtelnet_event_handler_t definition. More information about
events can be found in section IId.
The user_data parameter is passed to the event handler whenver it
is invoked. This will usually be a structure container
information about the connection, including a socket descriptor
for implementing LIBTELNET_EV_SEND event handling.
The flags parameter can be any of the following flag constants
bit-or'd together, or 0 to leave all options disabled.
2009-03-16 20:47:27 +00:00
LIBTELNET_FLAG_PROXY
Operate in proxy mode. This disables the RFC1143 support and
enables automatic detection of COMPRESS2 streams.
2009-03-14 17:39:07 +00:00
boid libtelnet_free(libtelnet_t *telnet);
2009-03-14 17:39:07 +00:00
Releases any internal memory allocated by libtelnet. This must
be called whenever a connection is closed, or you will incur
memory leaks.
IIb. Receiving Data
void libtelnet_push(libtelnet_t *telnet,
2009-03-16 20:51:41 +00:00
const unsigned char *buffer, unsigned int size, void *user_data);
2009-03-14 17:39:07 +00:00
When your application receives data over the socket from the
remote end, it must pass the received bytes into this function.
As the TELNET stream is parsed, events will be generated and
passed to the event handler given to libtelnet_init(). Of
particular interest for data receiving is the LIBTELNET_EV_DATA
event, which is triggered for any regular data such as user
input or server process output.
2009-03-14 17:39:07 +00:00
IIc. Sending Data
All of the libtelnet_send_*() functions will invoke the
LIBTELNET_EV_SEND event.
Note: it is very important that ALL data sent to the remote end of
the connection be passed through libtelnet. All user input or
process output that you wish to send over the wire should be given
to one of the following functions. Do NOT send or buffer
unprocessed output data directly!
2009-03-14 17:39:07 +00:00
void libtelnet_send_command(libtelnet_t *telnet, unsigned char cmd);
2009-03-14 17:39:07 +00:00
Sends a single "simple" TELNET command, such as the GO-AHEAD
commands (255 249).
void libtelnet_send_command(libtelnet_t *telnet, unsigned char cmd,
unsigned char telopt);
Sends a TELNET command with an option code following. This is
only useful for the WILL, WONT, DO, DONT, and SB commands.
void libtelnet_send_negotiate(libtelnet_t *telnet,
unsigned char cmd, unsigned char opt);
2009-03-14 17:39:07 +00:00
Sends a TELNET negotiation command. The cmd parameter must be
one of LIBTELNET_WILL, LIBTELNET_DONT, LIBTELNET_DO, or
LIBTELNET_DONT. The opt parameter is the option to
negotiate.
2009-03-16 20:51:41 +00:00
void libtelnet_send_data(libtelnet_t *telnet,
const unsigned char *buffer, unsigned int size);
2009-03-14 17:39:07 +00:00
Sends raw data, which would be either the process output from
a server or the user input from a client.
void libtelnet_send_subnegotiation(libtelnet_t *telnet,
2009-03-16 20:51:41 +00:00
unsigned char telopt, const unsigned char *buffer,
unsigned int size);
Sends a TELNET sub-negotiation command. The telopt parameter
2009-03-14 17:39:07 +00:00
is the sub-negotiation option.
Note that the above function is just a shorthand for:
libtelnet_send_telopt(telnet, LIBTELNET_SB, telopt);
libtelnet_send_data(telnet, buffer, size);
libtelnet_send_command(telnet, LIBTELNET_SE);
For some subnegotiations that involve a lot of complex formatted
data to be sent, it may be easier to manually send the SB telopt
header and SE footer around mulitple calls to send_data.
NOTE: libtelnet_send_subnegotiation() does have special behavior
in PROXY mode, as in that mode this function will automatically
detect the COMPRESS2 marker and enable zlib compression.
int libtelnet_printf(libtelnet_t *telnet, const char *fmt, ...);
This functions very similarly to fprintf, except that output
is sent through libtelnet for processing. This is equivalent
to using snprintf() to format data into a buffer and then
sending the buffer to libtelnet_send_data(). The return code
is the length of the formatted text.
NOTE: due to an internal implementation detail, the maximum
lenth of the formatted text is 4096 characters.
int libtelnet_printf2(libtelnet_T *telnet, const char *fmt, ...);
Identical to libtelnet_print(), except that this variant will
also translate C newlines (\n) into a CRLF and translates
carriage returns (\r) into CRNUL, as required by TELNET.
NOTE: this function should only be used for regular data such
as user input (in client applications) or process output (in
server applications). If you are formatting data that is part
of a subnegotiation, you should always use libtelnet_printf()
instead, as you will rarely want newline translation inside of
subnegotiations.
IId. Event Handling
2009-03-14 17:39:07 +00:00
libtelnet relies on an event-handling mechanism for processing
the parsed TELNET protocol stream as well as for buffering and
sending output data.
When you initialize a libtelnet_t structure with libtelnet_init()
you had to pass in an event handler function. This function must
meet the following prototype:
void (libtelnet_t *telnet, libtelnet_event_t *event,
void *user_data);
The event structure is detailed below. The user_data value is the
pointer passed to libtelnet_init().
struct libtelnet_event_t {
2009-03-16 20:51:41 +00:00
const unsigned char *buffer;
unsigned int size;
libtelnet_event_type_t type;
unsigned char command;
unsigned char telopt;
unsigned char accept;
};
The enumeration values of libtelnet_event_type_t are described in
detail below. Whenever the the event handler is invoked, the
application must look at the event->type value and do any
necessary processing.
The only event that MUST be implemented is LIBTELNET_EV_SEND.
Most applications will also always want to implement the event
LIBTELNET_EV_DATA.
Here is an example event handler implementation which includes
handlers for several important events.
void my_event_handler(libtelnet_t *telnet, libtelnet_event_t *ev,
void *user_data) {
struct user_info *user = (struct user_info *)user_data;
switch (ev->type) {
case LIBTELNET_EV_DATA:
process_user_input(user, event->buffer, event->size);
break;
case LIBTELNET_EV_SEND:
write_to_descriptor(user, event->buffer, event->size);
break;
case LIBTELNET_EV_ERROR:
fatal_error("TELNET error: %s", event->buffer);
break;
}
}
2009-03-14 17:39:07 +00:00
LIBTELNET_EV_DATA:
The DATA event is triggered whenever regular data (not part of
any special TELNET command) is received. For a client, this
will be process output from the server. For a server, this will
be input typed by the user.
2009-03-14 17:39:07 +00:00
The event->buffer value will contain the bytes received and the
event->size value will contain the number of bytes received.
Note that event->buffer is not NUL terminated!
2009-03-14 17:39:07 +00:00
NOTE: there is no guarantee that user input or server output
will be received in whole lines. If you wish to process data
a line at a time, you are responsible for buffering the data and
checking for line terminators yourself!
LIBTELNET_EV_SEND:
This event is sent whenever libtelnet has generated data that
must be sent over the wire to the remove end. Generally that
means calling send() or adding the data to your application's
output buffer.
The event->buffer value will contain the bytes to send and the
event->size value will contain the number of bytes to send.
Note that event->buffer is not NUL terminated, and may include
NUL characters in its data, so always use event->size!
NOTE: Your SEND event handler must send or buffer the data in
its raw form as provided by libtelnet. If you wish to perform
any kind of preprocessing on data you want to send to the other
LIBTELNET_EV_IAC:
The IAC event is triggered whenever a simple IAC command is
received, such as the IAC EOR (end of record, also called
go ahead or GA) command.
2009-03-14 17:39:07 +00:00
The command received is in the event->command value.
2009-03-14 17:39:07 +00:00
The necessary processing depends on the specific commands; see
the TELNET RFC for more information.
LIBTELNET_EV_WILL:
LIBTELNET_EV_DO:
The WILL and DO events are sent when a TELNET negotiation
command of the same name is received.
2009-03-14 17:39:07 +00:00
WILL events are sent by the remote end when they wish to be
allowed to turn an option on on their end, or in confirmation
after you have sent a DO command to them.
2009-03-14 17:39:07 +00:00
DO events are sent by the remote end when they wish for you
to turn on an option on your end, or in confirmation after you
have sent a WILL command to them.
In either case, the TELNET option under negotiation will be in
event->telopt field.
If you support the option and wish for it to be enabled you
must set the event->accept field to 1, unless this event is
a confirmation for a previous WILL/DO command you sent to the
remote end. If you do not set event->field to 1 then
libtelnet will send a rejection command back to the other end.
libtelnet manages some of the pecularities of negotiation for
you. For information on libtelnet's negotiation method, see:
2009-03-14 17:39:07 +00:00
http://www.faqs.org/rfcs/rfc1143.html
Examples:
You want remote end to use TTYPE, so you send DO TTYPE.
Remote accepts and sends WILL TTYPE.
Remote end wants you to use SGA, so they send DO_SGA.
You do not support SGA and set event->accept = 0.
Remote end wants to use ZMP, so they send WILL ZMP.
You support ZMP, so you set event->accept = 1 and enable
local ZMP support.
You want to use MCCP2, so you send WILL COMPRESS2.
Remote end accepts and sends DO COMPRESS2.
Note that in PROXY mode libtelnet will do no processing of its
own for you.
LIBTELNET_EV_WONT:
LIBTELNET_EV_DONT:
The WONT and DONT events are sent when the remote end of the
connection wishes to disable an option, when they are
refusing to a support an option that you have asked for, or
in confirmation of an option you have asked to be disabled.
Most commonly WONT and DONT events are sent as rejections of
features you requested by sending DO or WILL events. Receiving
these events means the TELNET option is not or will not be
supported by the remote end, so give up.
Sometimes WONT or DONT will be sent for TELNET options that are
already enabled, but the remote end wishes to stop using. You
cannot decline. These events are demands that must be complied
with. libtelnet will always send the appropriate response back
without consulting your application. These events are sent to
allow your application to disable its own use of the features.
In either case, the TELNET option under negotiation will be in
event->telopt field.
Note that in PROXY mode libtelnet will do no processing of its
own for you.
LIBTELNET_EV_SUBNEGOTIATION:
Triggered whenever a TELNET sub-negotiation has been received.
2009-03-14 17:39:07 +00:00
Sub-negotiations include the NAWS option for communicating
terminal size to a server, the NEW-ENVIRON and TTYPE options
for negotiating terminal features, and MUD-centric protocols
such as ZMP, MSSP, and MCCP2.
The event->telopt value is the option under sub-negotiation.
The remaining data (if any) is passed in event->buffer and
event->size. Note that most subnegotiation commands can
include embedded NUL bytes in the subnegotiation data, and
the data event->buffer is not NUL terminated, so always use
the event->size value!
The meaning and necessary processing for subnegotiations are
defined in various TELNET RFCs and other informal
specifications. A subnegotiation should never be sent unless
the specific option has been enabled through the use of the
telnet negotiation feature.
LIBTELNET_EV_COMPRESS
The COMPRESS event notifies the app that COMPRESS2/MCCP2
compression has begun or ended. Only servers can send compressed
data, and hence only clients will receive compressed data.
The event->command value will be 1 if compression has started and
will be 0 if compression has ended.
LIBTELNET_EV_WARNING
The WARNING event is sent whenever something has gone wrong
inside of libtelnet (possibly due to malformed data sent by the
other end) but which recovery is (likely) possible. It may be
safe to continue using the connection, but some data may have
been lost or incorrectly interpreted.
The event->buffer value will contain a NUL terminated string
explaining the error, and the event->size value containers the
length of the string.
LIBTELNET_EV_ERROR
Similar to the WARNING event, the ERROR event is sent whenever
something has gone wrong. ERROR events are non-recoverable,
however, and the application should immediately close the
connection. Whatever has happened is likely going only to
result in garbage from libtelnet. This is most likely to
happen when a COMPRESS2 stream fails, but other problems can
occur.
The event->buffer value will contain a NUL terminated string
explaining the error, and the event->size value containers the
length of the string.
III. INTEGRATING LIBTELNET WITH COMMON MUDS
2009-03-13 03:33:27 +00:00
=====================================================================
FIXME: fill in some notes about how to splice in libtelnet with
common Diku/Merc/Circle/etc. MUD codebases.
2009-03-13 03:33:27 +00:00
2009-03-14 17:39:07 +00:00
IV. SAFETY AND CORRECTNESS CONSIDERATIONS
2009-03-13 03:33:27 +00:00
=====================================================================
2009-03-14 17:39:07 +00:00
Your existing application may make heavy use of its own output
buffering and transmission commands, including hand-made routines
for sending TELNET commands and sub-negotiation requests. There are
at times subtle issues that need to be handled when communication
over the TELNET protocol, not least of which is the need to escape
any byte value 0xFF with a special TELNET command.
2009-03-13 03:33:27 +00:00
2009-03-14 17:39:07 +00:00
For these reasons, it is very important that applications making use
of libtelnet always make use of the libtelnet_send_*() family of
functions for all data being sent over the TELNET connection.
In particular, if you are writing a client, all user input must be
passed through to libtelnet_send_data(). This also includes any
input generated automatically by scripts, triggers, or macros.
For a server, any and all output -- including ANSI/VT100 escape
codes, regular text, newlines, and so on -- must be passed through
to libtelnet_send_data().
Any TELNET commands that are to be sent must be given to one of the
following: libtelnet_send_command, libtelnet_send_negotiate, or
libtelnet_send_subnegotiation().
If you are attempting to enable COMPRESS2/MCCP2, you must use the
libtelnet_begin_compress2() function.
2009-03-14 17:39:07 +00:00
V. MCCP2 COMPRESSION
2009-03-13 03:33:27 +00:00
=====================================================================
2009-03-14 17:39:07 +00:00
The MCCP2 (COMPRESS2) TELNET extension allows for the compression of
all traffic sent from server to client. For more information:
http://www.mudbytes.net/index.php?a=articles&s=mccp
In order for libtelnet to support MCCP2, zlib must be installed and
enabled when compiling libtelnet. Use -DHAVE_ZLIB to enable zlib
when compiling libtelnet.c and pass -lz to the linker to link in the
zlib shared library.
2009-03-14 17:39:07 +00:00
libtelnet transparently supports MCCP2. For a server to support
MCCP2, the application must begin negotiation of the COMPRESS2
option using libtelnet_send_negotiate(), for example:
libtelnet_send_negotiate(&telnet, LIBTELNET_WILL,
LIBTELNET_OPTION_COMPRESS2, user_data);
If a favorable DO COMPRESS2 is sent back from the client then the
server application can begin compression at any time by calling
libtelnet_begin_compress2().
2009-03-14 17:39:07 +00:00
If a connection is in PROXY mode and COMPRESS2 support is enabled
then libtelnet will automatically detect the start of a COMPRESS2
stream, in either the sending or receiving direction.
2009-03-14 17:39:07 +00:00
VI. TELNET PROXY UTILITY
=====================================================================
The telnet-proxy utility is a small application that serves both as
a testbed for libtelnet and as a powerful debugging tool for TELNET
servers and clients.
To use telnet-proxy, you must first compile it using:
$ make
If you do not have zlib installed and wish to disable MCCP2 support
then you must first edit the Makefile and remove the -DHAVE_ZLIB and
the -lz from the compile flags.
2009-03-15 05:06:17 +00:00
To run telnet-proxy, you simply give it the server's host name or
IP address, the server's port number, and the port number that
telnet-proxy should listen on. For example, to connect to the server
on mud.example.com port 7800 and to listen on port 5000, run:
2009-03-14 17:39:07 +00:00
2009-03-15 05:06:17 +00:00
$ ./telnet-proxy mud.example.com 7800 5000
2009-03-14 17:39:07 +00:00
You can then connect to the host telnet-proxy is running on (e.g.
2009-03-15 05:06:17 +00:00
127.0.0.1) on port 500 and you will automatically be proxied into
mud.example.com.
2009-03-14 17:39:07 +00:00
telnet-proxy will display status information about the data
passing through both ends of the tunnel. telnet-proxy can only
support a single tunnel at a time. It will continue running until
an error occurs or a terminating signal is sent to the proxy
process.