| ===================================================================== |
| libtelnet - TELNET protocol handling library |
| ===================================================================== |
| |
| http://github.com/elanthis/libtelnet |
| |
| Sean Middleditch |
| sean@sourcemud.org |
| |
| --------------------------------------------------------------------- |
| 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. |
| --------------------------------------------------------------------- |
| |
| *** TODO *** |
| |
| - automatic MCCP2 handling (controllable by host app) |
| ? ZMP parsing |
| ? MSSP parsing |
| ? ENVIRON/NEW-ENVIRON parsing |
| ? telnet-status testing tool |
| |
| I. INTRODUCTION |
| ===================================================================== |
| |
| libtelnet provides safe and correct handling of the core TELNET |
| 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. |
| |
| For more information on the TELNET protocol, see: |
| |
| http://www.faqs.org/rfcs/rfc854.html |
| http://www.faqs.org/rfcs/rfc1143.html |
| |
| II. LIBTELNET API |
| ===================================================================== |
| |
| 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 telnet |
| processor. The third part is the telnet_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. |
| |
| IIa. Initialization |
| |
| struct telnet_t; |
| This structure represents the state of the TELNET protocol for a |
| single connection. Each connection utilizing TELNET must have its |
| own telnet_t structure, which is passed to all libtelnet API |
| calls. |
| |
| void telnet_init(telnet_t *telnet, telnet_event_handler_t handler, |
| unsigned char flags, void *user_data); |
| The telnet_init() function is responsible for initializing the |
| data in a telnet_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 |
| telnet_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 TELNET_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. |
| |
| TELNET_FLAG_PROXY |
| Operate in proxy mode. This disables the RFC1143 support and |
| enables automatic detection of COMPRESS2 streams. |
| |
| boid telnet_free(telnet_t *telnet); |
| 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 telnet_push(telnet_t *telnet, |
| const char *buffer, unsigned int size, void *user_data); |
| 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 telnet_init(). Of particular |
| interest for data receiving is the TELNET_EV_DATA event, which is |
| triggered for any regular data such as user input or server |
| process output. |
| |
| IIc. Sending Data |
| |
| All of the telnet_send_*() functions will invoke the TELNET_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! |
| |
| void telnet_send_command(telnet_t *telnet, unsigned char cmd); |
| Sends a single "simple" TELNET command, such as the GO-AHEAD |
| commands (255 249). |
| |
| void telnet_send_telopt(telnet_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 telnet_send_negotiate(telnet_t *telnet, unsigned char cmd, |
| unsigned char opt); |
| Sends a TELNET negotiation command. The cmd parameter must be one |
| of TELNET_WILL, TELNET_DONT, TELNET_DO, or TELNET_DONT. The opt |
| parameter is the option to negotiate. |
| |
| Unless in PROXY mode, the RFC1143 support may delay or ellide the |
| request entirely, as appropriate. It will ignore duplicate |
| invocations, such as asking for WILL NAWS when NAWS is already on |
| or is currently awaiting response from the remote end. |
| |
| void telnet_send_data(telnet_t *telnet, const char *buffer, |
| unsigned int size); |
| Sends raw data, which would be either the process output from a |
| server or the user input from a client. |
| |
| For sending regular text is may be more convenient to use |
| telnet_printf(). |
| |
| void telnet_send_subnegotiation(telnet_t *telnet, |
| unsigned char telopt, const char *buffer, unsigned int size); |
| Sends a TELNET sub-negotiation command. The telopt parameter is |
| the sub-negotiation option. |
| |
| Note that the above function is just a shorthand for: |
| telnet_send_telopt(telnet, TELNET_SB, telopt); |
| telnet_send_data(telnet, buffer, size); |
| telnet_send_command(telnet, TELNET_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: telnet_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 telnet_printf(telnet_t *telnet, const char *fmt, ...); |
| This functions very similarly to fprintf, except that output is |
| sent through libtelnet for processing. IAC bytes are properly |
| escaped, C newlines (\n) are translated into CR LF, and C carriage |
| returns (\r) are translated into CR NUL, all as required by |
| RFC854. 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 telnet_printf2(telnet_t *telnet, const char *fmt, ...); |
| Identical to telnet_printf() except that \r and \n are not |
| translated. This should be used if you are attempting to send |
| raw data inside a subnegotiation or if you have already manually |
| escaped newlines. |
| |
| IId. Event Handling |
| |
| 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 telnet_t structure with telnet_init() you had |
| to pass in an event handler function. This function must meet the |
| following prototype: |
| |
| void (telnet_t *telnet, telnet_event_t *event, void *user_data); |
| |
| The event structure is detailed below. The user_data value is the |
| pointer passed to telnet_init(). |
| |
| struct telnet_event_t { |
| const char *buffer; |
| unsigned int size; |
| telnet_event_type_t type; |
| unsigned char command; |
| unsigned char telopt; |
| unsigned char accept; |
| }; |
| |
| The enumeration values of telnet_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 TELNET_EV_SEND. Most |
| applications will also always want to implement the event |
| TELNET_EV_DATA. |
| |
| Here is an example event handler implementation which includes |
| handlers for several important events. |
| |
| void my_event_handler(telnet_t *telnet, telnet_event_t *ev, |
| void *user_data) { |
| struct user_info *user = (struct user_info *)user_data; |
| |
| switch (ev->type) { |
| case TELNET_EV_DATA: |
| process_user_input(user, event->buffer, event->size); |
| break; |
| case TELNET_EV_SEND: |
| write_to_descriptor(user, event->buffer, event->size); |
| break; |
| case TELNET_EV_ERROR: |
| fatal_error("TELNET error: %s", event->buffer); |
| break; |
| } |
| } |
| |
| TELNET_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. |
| |
| 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! |
| |
| 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! |
| |
| TELNET_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 |
| |
| TELNET_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. |
| |
| The command received is in the event->command value. |
| |
| The necessary processing depends on the specific commands; see |
| the TELNET RFC for more information. |
| |
| TELNET_EV_WILL: |
| TELNET_EV_DO: |
| The WILL and DO events are sent when a TELNET negotiation command |
| of the same name is received. |
| |
| 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. |
| |
| 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: |
| |
| 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. |
| |
| TELNET_EV_WONT: |
| TELNET_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. |
| |
| TELNET_EV_SUBNEGOTIATION: |
| Triggered whenever a TELNET sub-negotiation has been received. |
| 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. |
| |
| TELNET_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. |
| |
| TELNET_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. |
| |
| TELNET_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 |
| ===================================================================== |
| |
| FIXME: fill in some notes about how to splice in libtelnet with |
| common Diku/Merc/Circle/etc. MUD codebases. |
| |
| IV. SAFETY AND CORRECTNESS CONSIDERATIONS |
| ===================================================================== |
| |
| 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. |
| |
| For these reasons, it is very important that applications making use |
| of libtelnet always make use of the telnet_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 telnet_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 |
| telnet_send_data(). |
| |
| Any TELNET commands that are to be sent must be given to one of the |
| following: telnet_send_command, telnet_send_negotiate, or |
| telnet_send_subnegotiation(). |
| |
| If you are attempting to enable COMPRESS2/MCCP2, you must use the |
| telnet_begin_compress2() function. |
| |
| V. MCCP2 COMPRESSION |
| ===================================================================== |
| |
| 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. |
| |
| libtelnet transparently supports MCCP2. For a server to support |
| MCCP2, the application must begin negotiation of the COMPRESS2 option |
| using telnet_send_negotiate(), for example: |
| |
| telnet_send_negotiate(&telnet, TELNET_WILL, |
| TELNET_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 |
| telnet_begin_compress2(). |
| |
| 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. |
| |
| 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. |
| |
| 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: |
| |
| $ ./telnet-proxy mud.example.com 7800 5000 |
| |
| You can then connect to the host telnet-proxy is running on (e.g. |
| 127.0.0.1) on port 500 and you will automatically be proxied into |
| mud.example.com. |
| |
| 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. |