diff --git a/Project.swift b/Project.swift index 5112525..e5eee3f 100644 --- a/Project.swift +++ b/Project.swift @@ -1,10 +1,15 @@ import ProjectDescription +import Foundation + +let mthPackage: ProjectDescription.Package = FileManager.default.fileExists(atPath: "../mth/Package.swift") + ? .local(path: "../mth") + : .remote(url: "https://github.com/ncmud/mth.git", requirement: .exact("2.1.0")) let project = Project( name: "Wammer", packages: [ .local(path: "Vendored/CocoaAsyncSocket"), - .local(path: "Vendored/libtelnet"), + mthPackage, .local(path: "Vendored/SAMRateLimit"), .local(path: "Vendored/KVOController"), .local(path: "Vendored/OSCache"), @@ -110,11 +115,12 @@ let project = Project( "src/Mudrammer/Supporting Files/it.lproj/**", "src/Mudrammer/Images.xcassets", "src/Mudrammer/Supporting Files/LaunchScreen.storyboard", + "src/Mudrammer/Supporting Files/Pods-acknowledgements.plist", ], entitlements: "src/Mudrammer/Supporting Files/MUDRammer.entitlements", dependencies: [ .package(product: "CocoaAsyncSocket"), - .package(product: "libtelnet"), + .package(product: "MTHClient"), .package(product: "SAMRateLimit"), .package(product: "KVOController"), .package(product: "OSCache"), diff --git a/Vendored/libtelnet/LICENSE b/Vendored/libtelnet/LICENSE deleted file mode 100644 index afd1081..0000000 --- a/Vendored/libtelnet/LICENSE +++ /dev/null @@ -1,646 +0,0 @@ -===================================================================== - libtelnet - TELNET protocol handling library -===================================================================== - - http://github.com/seanmiddleditch/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. ---------------------------------------------------------------------- - -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 standards supported by libtelnet, -visit the following websites: - - http://www.faqs.org/rfcs/rfc854.html - http://www.faqs.org/rfcs/rfc855.html - http://www.faqs.org/rfcs/rfc1091.html - http://www.faqs.org/rfcs/rfc1143.html - http://www.faqs.org/rfcs/rfc1408.html - http://www.faqs.org/rfcs/rfc1572.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 libtelnet output functions, which -generate TELNET commands and ensure data is properly formatted before -sending over the wire. The final part is the event handler -interface. - -This document covers only the most basic functions. See the -libtelnet manual pages or HTML documentation for a complete -reference. - -IIa. Initialization - - Using libtelnet requires the initialization of a telnet_t structure - which stores all current state for a single TELNET connection. - - Initializing a telnet_t structure requires several pieces of data. - One of these is the telopt support table, which specifies which - TELNET options your application supports both locally and remotely. - This table is comprised of telnet_telopt_t structures, one for each - supported option. Each entry specifies the option supported, - whether the option is supported locally or remotely. - - struct telnet_telopt_t { - short telopt; - unsigned char us; - unsigned char him; - }; - - The us field denotes whether your application supports the telopt - locally. It should be set to TELNET_WILL if you support it and to - TELNET_WONT if you don't. The him field denotes whether the telopt - is supported on the remote end, and should be TELNET_DO if yes and - TELNET_DONT if not. - - When definition the telopt table you must include an end marker - entry, which is simply an entry with telopt set to -1. For - example: - - static const telnet_telopt_t my_telopts[] = { - { TELNET_TELOPT_ECHO, TELNET_WILL, TELNET_DONT }, - { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, - { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO }, - { TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DO }, - { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO }, - { TELNET_TELOPT_BINARY, TELNET_WILL, TELNET_DO }, - { TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DONT }, - { -1, 0, 0 } - }; - - If you need to dynamically alter supported options on a - per-connection basis then you may use a different table - (dynamically allocated if necessary) per call to telnet_init() or - you share a single constant table like the above example between - all connections if you support a fixed set of options. Most - applications will support only a fixed set of options. - - telnet_t *telnet_init(const telnet_telopts_t *telopts, - telnet_event_handler_t handler, unsigned char flags, - void *user_data); - The telnet_init() function is responsible for allocating memory - and 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 telopts field is the telopt support table as described above. - - 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. - - If telnet_init() fails to allocate the required memory, the - returned pointer will be zero. - - void telnet_free(telnet_t *telnet); - Releases any internal memory allocated by libtelnet for the given - telnet pointer. This must be called whenever a connection is - closed, or you will incur memory leaks. The pointer passed in may - no longer be used afterwards. - -IIb. Receiving Data - - void telnet_recv(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 output 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_iac(telnet_t *telnet, unsigned char cmd); - Sends a single "simple" TELNET command, such as the GO-AHEAD - commands (255 249). - - void telnet_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_WONT, 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(telnet_t *telnet, const char *buffer, size_t 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_begin_sb(telnet_t *telnet, unsigned char telopt); - Sends the header for a TELNET sub-negotiation command for the - specified option. All send data following this command will be - part of the sub-negotiation data until a call is made to - telnet_finish_subnegotiation(). - - You should not use telnet_printf() for sending subnegotiation - data as it will perform newline translations that usually do not - need to be done for subnegotiation data, and may cause problems. - - void telnet_finish_sb(telnet_t *telnet); - Sends the end marker for a TELNET sub-negotiation command. This - must be called after (and only after) a call has been made to - telnet_begin_subnegotiation() and any negotiation data has been - sent. - - void telnet_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 this function is just a shorthand for: - telnet_begin_sb(telnet, telopt); - telnet_send(telnet, buffer, size); - telnet_end_sb(telnet); - - For some subnegotiations that involve a lot of complex formatted - data to be sent, it may be easier to make calls to both - telnet_begin_sb() and telnet_finish_sb() and using telnet_send() - or telnet_printf2() to format the data. - - NOTE: telnet_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. - -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(). - - The following is a summary of the most important parts of the - telnet_event_t data type. Please see the libtelnet manual pages or - HTML document for a complete reference. - - union telnet_event_t { - enum telnet_event_type_t type; - - struct data_t { - enum telnet_event_type_t _type; - const char *buffer; - size_t size; - } data; - - struct error_t { - enum telnet_event_type_t _type; - const char *file; - const char *func; - const char *msg; - int line; - telnet_error_t errcode; - } error; - - struct iac_t { - enum telnet_event_type_t _type; - unsigned char cmd; - } iac; - - struct negotiate_t { - enum telnet_event_type_t _type; - unsigned char telopt; - } neg; - - struct subnegotiate_t { - enum telnet_event_type_t _type; - const char *buffer; - size_t size; - unsigned char telopt; - } sub; - }; - - 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->data.buffer, event->data.size); - break; - case TELNET_EV_SEND: - write_to_descriptor(user, event->data.buffer, event->data.size); - break; - case TELNET_EV_ERROR: - fatal_error("TELNET error: %s", event->error.msg); - 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->data.buffer value will contain the bytes received and the - event->data.size value will contain the number of bytes received. - Note that event->data.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->data.buffer value will contain the bytes to send and the - event->data.size value will contain the number of bytes to send. - Note that event->data.buffer is not NUL terminated, and may include - NUL characters in its data, so always use event->data.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->iac.cmd 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->neg.telopt field. - - libtelnet manages most of the pecularities of negotiation for you. - For information on libtelnet's negotiation method, see: - - http://www.faqs.org/rfcs/rfc1143.html - - 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->neg.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->sub->telopt value is the option under sub-negotiation. - The remaining data (if any) is passed in event->sub.buffer and - event->sub.size. Note that most subnegotiation commands can include - embedded NUL bytes in the subnegotiation data, and the data - event->sub.buffer is not NUL terminated, so always use the - event->sub.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. - - TTYPE/ENVIRON/NEW-ENVIRON/MSSP/ZMP SUPPORT: - libtelnet parses these subnegotiation commands. A special - event will be sent for each, after the SUBNEGOTIATION event is - sent. Except in special circumstances, the SUBNEGOTIATION event - should be ignored for these options and the special events should - be handled explicitly. - - 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_ZMP: - The event->zmp.argc field is the number of ZMP parameters, including - the command name, that have been received. The event->zmp.argv - field is an array of strings, one for each ZMP parameter. The - command name will be in event->zmp.argv[0]. - - TELNET_EV_TTYPE: - The event->ttype.cmd field will be either TELNET_TTYPE_SEND, - TELNET_TTYPE_IS, TELNET_TTYPE_INFO. - - The actual terminal type will be in event->ttype.name. - - TELNET_EV_ENVIRON: - The event->environ.cmd field will be either TELNET_ENVIRON_IS, - TELNET_ENVIRON_SEND, or TELNET_ENVIRON_INFO. - - The actual environment variable sent or requested will be sent - in the event->environ.values field. This is an array of - structures with the following format: - - struct telnet_environ_t { - unsigned char type; - const char *var; - const char *value; - }; - - The number of entries in the event->environ.values array is - stored in event->environ.count. - - Note that libtelnet does not support the ESC byte for ENVIRON/ - NEW-ENVIRON. Data using escaped bytes will not be parsed - correctly. - - TELNET_EV_MSSP: - The event->mssp.values field is an array of telnet_environ_t - structures. The cmd field in each entry will have an - unspecified value, while the var and value fields will always - be set to the MSSP variable and value being set. For multi-value - MSSP variables, there will be multiple entries in the values - array for each value, each with the same variable name set. - - The number of entries in the event->mssp.values array is - stored in event->mssp.count. - - 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->error.msg field will contain a NUL terminated string - explaining the error. - - 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->error.msg field will contain a NUL terminated string - explaining the error. - -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 libtelnet output 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(). 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(). - -Any TELNET commands that are to be sent must be given to one of the -following: telnet_iac, telnet_negotiate, or telnet_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_negotiate(), for example: - - telnet_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. ZENITH MUD PROTOCOL (ZMP) SUPPORT -===================================================================== - -The Zenith MUD Protocol allows applications to send messages across -the TELNET connection outside of the normal user input/output data -stream. libtelnet offers some limited support for receiving and -sending ZMP commands to make implementing a full ZMP stack easier. -For more information on ZMP: - - http://zmp.sourcemud.org/ - -For a server to enable ZMP, it must send the WILL ZMP negotitaion: - - telnet_negotiate(&telnet, TELNET_WILL, TELNET_TELOPT_ZMP); - -For a client to support ZMP it must include ZMP in the telopt table -passed to telnet_init(), with the him field set to TELNET_DO: - - { TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DO }, - -Note that while ZMP is a bi-directional protocol, it is only ever -enabled on the server end of the connection. This automatically -enables the client to send ZMP commands. The client must never -attempt to negotiate ZMP directly using telnet_negotiate(). - -Once ZMP is enabled, any ZMP commands received will automatically be -sent to the event handler function with the TELNET_EV_SUBNEGOTIATION -event code. The command will automatically be parsed and the ZMP -parameters will be placed in the event->argv array and the number of -parameters will be placed in the event->argc field. - -NOTE: if an error occured while parsing the ZMP command because it -was malformed, the event->argc field will be equal to 0 and the -event->argv field will be NULL. You should always check for this -before attempting to access the parameter array. - -To send ZMP commands to the remote end, use either telnet_send_zmp() -or telnet_send_zmpv(). - - int telnet_send_zmp(telnet_t *telnet, size_t argv, - const char **argv); - Sends a ZMP command to the remote end. The argc parameter is the - number of ZMP parameters (including the command name!) to be sent. - The argv parameter is an array of strings containing the - parameters. The element in argv[0] is the command name itself. - The argv array must have at least as many elements as the value - argc. - -VII. MUD SERVER STATUS PROTOCOL (MSSP) SUPPORT -===================================================================== - -MSSP allows for crawlers or other clients to query a MUD server's -supported feature list. This allows MUD listing states to -automatically stay up to date with the MUD's features, and not -require MUD administrators to manually update listing sites for -their MUD. For more information on MSSP: - - http://tintin.sourceforge.net/mssp/ - -VIII. 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 5000 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. diff --git a/Vendored/libtelnet/Package.swift b/Vendored/libtelnet/Package.swift deleted file mode 100644 index 2d6d473..0000000 --- a/Vendored/libtelnet/Package.swift +++ /dev/null @@ -1,16 +0,0 @@ -// swift-tools-version: 6.2 -import PackageDescription -let package = Package( - name: "libtelnet", - platforms: [.iOS(.v26), .visionOS(.v26)], - products: [.library(name: "libtelnet", targets: ["libtelnet"])], - targets: [ - .target( - name: "libtelnet", - path: "Sources", - publicHeadersPath: "include", - cSettings: [.headerSearchPath("."), .define("HAVE_ZLIB"), .unsafeFlags(["-Wmost"])], - linkerSettings: [.linkedLibrary("z")] - ) - ] -) diff --git a/Vendored/libtelnet/Sources/include/libtelnet.h b/Vendored/libtelnet/Sources/include/libtelnet.h deleted file mode 100644 index abc7d16..0000000 --- a/Vendored/libtelnet/Sources/include/libtelnet.h +++ /dev/null @@ -1,673 +0,0 @@ -/*! - * \brief libtelnet - TELNET protocol handling library - * - * SUMMARY: - * - * libtelnet is a library for handling the TELNET protocol. It includes - * routines for parsing incoming data from a remote peer as well as formatting - * data to send to the remote peer. - * - * libtelnet uses a callback-oriented API, allowing application-specific - * handling of various events. The callback system is also used for buffering - * outgoing protocol data, allowing the application to maintain control over - * the actual socket connection. - * - * Features supported include the full TELNET protocol, Q-method option - * negotiation, ZMP, MCCP2, MSSP, and NEW-ENVIRON. - * - * CONFORMS TO: - * - * RFC854 - http://www.faqs.org/rfcs/rfc854.html - * RFC855 - http://www.faqs.org/rfcs/rfc855.html - * RFC1091 - http://www.faqs.org/rfcs/rfc1091.html - * RFC1143 - http://www.faqs.org/rfcs/rfc1143.html - * RFC1408 - http://www.faqs.org/rfcs/rfc1408.html - * RFC1572 - http://www.faqs.org/rfcs/rfc1572.html - * - * LICENSE: - * - * 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. - * - * \file libtelnet.h - * - * \version 0.21 - * - * \author Sean Middleditch - */ - -#if !defined(LIBTELNET_INCLUDE) -#define LIBTELNET_INCLUDE 1 - -/* standard C headers necessary for the libtelnet API */ -#include -#include - -/* C++ support */ -#if defined(__cplusplus) -extern "C" { -#endif - -/* printf type checking feature in GCC and some other compilers */ -#if __GNUC__ -# define TELNET_GNU_PRINTF(f,a) __attribute__((format(printf, f, a))) /*!< internal helper */ -# define TELNET_GNU_SENTINEL __attribute__((sentinel)) /*!< internal helper */ -#else -# define TELNET_GNU_PRINTF(f,a) /*!< internal helper */ -# define TELNET_GNU_SENTINEL /*!< internal helper */ -#endif - -/*! Telnet state tracker object type. */ -typedef struct telnet_t telnet_t; - -/*! Telnet event object type. */ -typedef union telnet_event_t telnet_event_t; - -/*! Telnet option table element type. */ -typedef struct telnet_telopt_t telnet_telopt_t; - -/*! \name Telnet commands */ -/*@{*/ -/*! Telnet commands and special values. */ -#define TELNET_IAC 255 -#define TELNET_DONT 254 -#define TELNET_DO 253 -#define TELNET_WONT 252 -#define TELNET_WILL 251 -#define TELNET_SB 250 -#define TELNET_GA 249 -#define TELNET_EL 248 -#define TELNET_EC 247 -#define TELNET_AYT 246 -#define TELNET_AO 245 -#define TELNET_IP 244 -#define TELNET_BREAK 243 -#define TELNET_DM 242 -#define TELNET_NOP 241 -#define TELNET_SE 240 -#define TELNET_EOR 239 -#define TELNET_ABORT 238 -#define TELNET_SUSP 237 -#define TELNET_EOF 236 -/*@}*/ - -/*! \name Telnet option values. */ -/*@{*/ -/*! Telnet options. */ -#define TELNET_TELOPT_BINARY 0 -#define TELNET_TELOPT_ECHO 1 -#define TELNET_TELOPT_RCP 2 -#define TELNET_TELOPT_SGA 3 -#define TELNET_TELOPT_NAMS 4 -#define TELNET_TELOPT_STATUS 5 -#define TELNET_TELOPT_TM 6 -#define TELNET_TELOPT_RCTE 7 -#define TELNET_TELOPT_NAOL 8 -#define TELNET_TELOPT_NAOP 9 -#define TELNET_TELOPT_NAOCRD 10 -#define TELNET_TELOPT_NAOHTS 11 -#define TELNET_TELOPT_NAOHTD 12 -#define TELNET_TELOPT_NAOFFD 13 -#define TELNET_TELOPT_NAOVTS 14 -#define TELNET_TELOPT_NAOVTD 15 -#define TELNET_TELOPT_NAOLFD 16 -#define TELNET_TELOPT_XASCII 17 -#define TELNET_TELOPT_LOGOUT 18 -#define TELNET_TELOPT_BM 19 -#define TELNET_TELOPT_DET 20 -#define TELNET_TELOPT_SUPDUP 21 -#define TELNET_TELOPT_SUPDUPOUTPUT 22 -#define TELNET_TELOPT_SNDLOC 23 -#define TELNET_TELOPT_TTYPE 24 -#define TELNET_TELOPT_EOR 25 -#define TELNET_TELOPT_TUID 26 -#define TELNET_TELOPT_OUTMRK 27 -#define TELNET_TELOPT_TTYLOC 28 -#define TELNET_TELOPT_3270REGIME 29 -#define TELNET_TELOPT_X3PAD 30 -#define TELNET_TELOPT_NAWS 31 -#define TELNET_TELOPT_TSPEED 32 -#define TELNET_TELOPT_LFLOW 33 -#define TELNET_TELOPT_LINEMODE 34 -#define TELNET_TELOPT_XDISPLOC 35 -#define TELNET_TELOPT_ENVIRON 36 -#define TELNET_TELOPT_AUTHENTICATION 37 -#define TELNET_TELOPT_ENCRYPT 38 -#define TELNET_TELOPT_NEW_ENVIRON 39 -#define TELNET_TELOPT_MSSP 70 -#define TELNET_TELOPT_COMPRESS 85 -#define TELNET_TELOPT_COMPRESS2 86 -#define TELNET_TELOPT_ZMP 93 -#define TELNET_TELOPT_EXOPL 255 - -#define TELNET_TELOPT_MCCP2 86 -/*@}*/ - -/*! \name Protocol codes for TERMINAL-TYPE commands. */ -/*@{*/ -/*! TERMINAL-TYPE codes. */ -#define TELNET_TTYPE_IS 0 -#define TELNET_TTYPE_SEND 1 -/*@}*/ - -/*! \name Protocol codes for NEW-ENVIRON/ENVIRON commands. */ -/*@{*/ -/*! NEW-ENVIRON/ENVIRON codes. */ -#define TELNET_ENVIRON_IS 0 -#define TELNET_ENVIRON_SEND 1 -#define TELNET_ENVIRON_INFO 2 -#define TELNET_ENVIRON_VAR 0 -#define TELNET_ENVIRON_VALUE 1 -#define TELNET_ENVIRON_ESC 2 -#define TELNET_ENVIRON_USERVAR 3 -/*@}*/ - -/*! \name Protocol codes for MSSP commands. */ -/*@{*/ -/*! MSSP codes. */ -#define TELNET_MSSP_VAR 1 -#define TELNET_MSSP_VAL 2 -/*@}*/ - -/*! \name Telnet state tracker flags. */ -/*@{*/ -/*! Control behavior of telnet state tracker. */ -#define TELNET_FLAG_PROXY (1<<0) - -#define TELNET_PFLAG_DEFLATE (1<<7) -/*@}*/ - -/*! - * error codes - */ -enum telnet_error_t { - TELNET_EOK = 0, /*!< no error */ - TELNET_EBADVAL, /*!< invalid parameter, or API misuse */ - TELNET_ENOMEM, /*!< memory allocation failure */ - TELNET_EOVERFLOW, /*!< data exceeds buffer size */ - TELNET_EPROTOCOL, /*!< invalid sequence of special bytes */ - TELNET_ECOMPRESS /*!< error handling compressed streams */ -}; -typedef enum telnet_error_t telnet_error_t; /*!< Error code type. */ - -/*! - * event codes - */ -enum telnet_event_type_t { - TELNET_EV_DATA = 0, /*!< raw text data has been received */ - TELNET_EV_SEND, /*!< data needs to be sent to the peer */ - TELNET_EV_IAC, /*!< generic IAC code received */ - TELNET_EV_WILL, /*!< WILL option negotiation received */ - TELNET_EV_WONT, /*!< WONT option neogitation received */ - TELNET_EV_DO, /*!< DO option negotiation received */ - TELNET_EV_DONT, /*!< DONT option negotiation received */ - TELNET_EV_SUBNEGOTIATION, /*!< sub-negotiation data received */ - TELNET_EV_COMPRESS, /*!< compression has been enabled */ - TELNET_EV_ZMP, /*!< ZMP command has been received */ - TELNET_EV_TTYPE, /*!< TTYPE command has been received */ - TELNET_EV_ENVIRON, /*!< ENVIRON command has been received */ - TELNET_EV_MSSP, /*!< MSSP command has been received */ - TELNET_EV_WARNING, /*!< recoverable error has occured */ - TELNET_EV_ERROR /*!< non-recoverable error has occured */ -}; -typedef enum telnet_event_type_t telnet_event_type_t; /*!< Telnet event type. */ - -/*! - * environ/MSSP command information - */ -struct telnet_environ_t { - unsigned char type; /*!< either TELNET_ENVIRON_VAR or TELNET_ENVIRON_USERVAR */ - char *var; /*!< name of the variable being set */ - char *value; /*!< value of variable being set; empty string if no value */ -}; - -/*! - * event information - */ -union telnet_event_t { - /*! - * \brief Event type - * - * The type field will determine which of the other event structure fields - * have been filled in. For instance, if the event type is TELNET_EV_ZMP, - * then the zmp event field (and ONLY the zmp event field) will be filled - * in. - */ - enum telnet_event_type_t type; - - /*! - * data event: for DATA and SEND events - */ - struct data_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char *buffer; /*!< byte buffer */ - size_t size; /*!< number of bytes in buffer */ - } data; - - /*! - * WARNING and ERROR events - */ - struct error_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char *file; /*!< file the error occured in */ - const char *func; /*!< function the error occured in */ - const char *msg; /*!< error message string */ - int line; /*!< line of file error occured on */ - telnet_error_t errcode; /*!< error code */ - } error; - - /*! - * command event: for IAC - */ - struct iac_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char cmd; /*!< telnet command received */ - } iac; - - /*! - * negotiation event: WILL, WONT, DO, DONT - */ - struct negotiate_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char telopt; /*!< option being negotiated */ - } neg; - - /*! - * subnegotiation event - */ - struct subnegotiate_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char *buffer; /*!< data of sub-negotiation */ - size_t size; /*!< number of bytes in buffer */ - unsigned char telopt; /*!< option code for negotiation */ - } sub; - - /*! - * ZMP event - */ - struct zmp_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char **argv; /*!< array of argument string */ - size_t argc; /*!< number of elements in argv */ - } zmp; - - /*! - * TTYPE event - */ - struct ttype_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char cmd; /*!< TELNET_TTYPE_IS or TELNET_TTYPE_SEND */ - const char* name; /*!< terminal type name (IS only) */ - } ttype; - - /*! - * COMPRESS event - */ - struct compress_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char state; /*!< 1 if compression is enabled, - 0 if disabled */ - } compress; - - /*! - * ENVIRON/NEW-ENVIRON event - */ - struct environ_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const struct telnet_environ_t *values; /*!< array of variable values */ - size_t size; /*!< number of elements in values */ - unsigned char cmd; /*!< SEND, IS, or INFO */ - } environ; - - /*! - * MSSP event - */ - struct mssp_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const struct telnet_environ_t *values; /*!< array of variable values */ - size_t size; /*!< number of elements in values */ - } mssp; -}; - -/*! - * \brief event handler - * - * This is the type of function that must be passed to - * telnet_init() when creating a new telnet object. The - * function will be invoked once for every event generated - * by the libtelnet protocol parser. - * - * \param telnet The telnet object that generated the event - * \param event Event structure with details about the event - * \param user_data User-supplied pointer - */ -typedef void (*telnet_event_handler_t)(telnet_t *telnet, - telnet_event_t *event, void *user_data); - -/*! - * telopt support table element; use telopt of -1 for end marker - */ -struct telnet_telopt_t { - short telopt; /*!< one of the TELOPT codes or -1 */ - unsigned char us; /*!< TELNET_WILL or TELNET_WONT */ - unsigned char him; /*!< TELNET_DO or TELNET_DONT */ -}; - -/*! - * state tracker -- private data structure - */ -struct telnet_t; - -/*! - * \brief Initialize a telnet state tracker. - * - * This function initializes a new state tracker, which is used for all - * other libtelnet functions. Each connection must have its own - * telnet state tracker object. - * - * \param telopts Table of TELNET options the application supports. - * \param eh Event handler function called for every event. - * \param flags 0 or TELNET_FLAG_PROXY. - * \param user_data Optional data pointer that will be passsed to eh. - * \return Telent state tracker object. - */ -extern telnet_t* telnet_init(const telnet_telopt_t *telopts, - telnet_event_handler_t eh, unsigned char flags, void *user_data); - -/*! - * \brief Free up any memory allocated by a state tracker. - * - * This function must be called when a telnet state tracker is no - * longer needed (such as after the connection has been closed) to - * release any memory resources used by the state tracker. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_free(telnet_t *telnet); - -/*! - * \brief Push a byte buffer into the state tracker. - * - * Passes one or more bytes to the telnet state tracker for - * protocol parsing. The byte buffer is most often going to be - * the buffer that recv() was called for while handling the - * connection. - * - * \param telnet Telnet state tracker object. - * \param buffer Pointer to byte buffer. - * \param size Number of bytes pointed to by buffer. - */ -extern void telnet_recv(telnet_t *telnet, const char *buffer, - size_t size); - -/*! - * \brief Send a telnet command. - * - * \param telnet Telnet state tracker object. - * \param cmd Command to send. - */ -extern void telnet_iac(telnet_t *telnet, unsigned char cmd); - -/*! - * \brief Send negotiation command. - * - * Internally, libtelnet uses RFC1143 option negotiation rules. - * The negotiation commands sent with this function may be ignored - * if they are determined to be redundant. - * - * \param telnet Telnet state tracker object. - * \param cmd TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT. - * \param opt One of the TELNET_TELOPT_* values. - */ -extern void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char opt); - -/*! - * Send non-command data (escapes IAC bytes). - * - * \param telnet Telnet state tracker object. - * \param buffer Buffer of bytes to send. - * \param size Number of bytes to send. - */ -extern void telnet_send(telnet_t *telnet, - const char *buffer, size_t size); - -/*! - * \brief Begin a sub-negotiation command. - * - * Sends IAC SB followed by the telopt code. All following data sent - * will be part of the sub-negotiation, until telnet_finish_sb() is - * called. - * - * \param telnet Telnet state tracker object. - * \param telopt One of the TELNET_TELOPT_* values. - */ -extern void telnet_begin_sb(telnet_t *telnet, - unsigned char telopt); - -/*! - * \brief Finish a sub-negotiation command. - * - * This must be called after a call to telnet_begin_sb() to finish a - * sub-negotiation command. - * - * \param telnet Telnet state tracker object. - */ -#define telnet_finish_sb(telnet) telnet_iac((telnet), TELNET_SE) - -/*! - * \brief Shortcut for sending a complete subnegotiation buffer. - * - * Equivalent to: - * telnet_begin_sb(telnet, telopt); - * telnet_send(telnet, buffer, size); - * telnet_finish_sb(telnet); - * - * \param telnet Telnet state tracker format. - * \param telopt One of the TELNET_TELOPT_* values. - * \param buffer Byte buffer for sub-negotiation data. - * \param size Number of bytes to use for sub-negotiation data. - */ -extern void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, - const char *buffer, size_t size); - -/*! - * \brief Begin sending compressed data. - * - * This function will begein sending data using the COMPRESS2 option, - * which enables the use of zlib to compress data sent to the client. - * The client must offer support for COMPRESS2 with option negotiation, - * and zlib support must be compiled into libtelnet. - * - * Only the server may call this command. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_begin_compress2(telnet_t *telnet); - -/*! - * \brief Send formatted data. - * - * This function is a wrapper around telnet_send(). It allows using - * printf-style formatting. - * - * Additionally, this function will translate \\r to the CR NUL construct and - * \\n with CR LF, as well as automatically escaping IAC bytes like - * telnet_send(). - * - * \param telnet Telnet state tracker object. - * \param fmt Format string. - * \return Number of bytes sent. - */ -extern int telnet_printf(telnet_t *telnet, const char *fmt, ...) - TELNET_GNU_PRINTF(2, 3); - -/*! - * \brief Send formatted data. - * - * See telnet_printf(). - */ -extern int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va); - -/*! - * \brief Send formatted data (no newline escaping). - * - * This behaves identically to telnet_printf(), except that the \\r and \\n - * characters are not translated. The IAC byte is still escaped as normal - * with telnet_send(). - * - * \param telnet Telnet state tracker object. - * \param fmt Format string. - * \return Number of bytes sent. - */ -extern int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) - TELNET_GNU_PRINTF(2, 3); - -/*! - * \brief Send formatted data (no newline escaping). - * - * See telnet_raw_printf(). - */ -extern int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va); - -/*! - * \brief Begin a new set of NEW-ENVIRON values to request or send. - * - * This function will begin the sub-negotiation block for sending or - * requesting NEW-ENVIRON values. - * - * The telnet_finish_newenviron() macro must be called after this - * function to terminate the NEW-ENVIRON command. - * - * \param telnet Telnet state tracker object. - * \param type One of TELNET_ENVIRON_SEND, TELNET_ENVIRON_IS, or - * TELNET_ENVIRON_INFO. - */ -extern void telnet_begin_newenviron(telnet_t *telnet, unsigned char type); - -/*! - * \brief Send a NEW-ENVIRON variable name or value. - * - * This can only be called between calls to telnet_begin_newenviron() and - * telnet_finish_newenviron(). - * - * \param telnet Telnet state tracker object. - * \param type One of TELNET_ENVIRON_VAR, TELNET_ENVIRON_USERVAR, or - * TELNET_ENVIRON_VALUE. - * \param string Variable name or value. - */ -extern void telnet_newenviron_value(telnet_t* telnet, unsigned char type, - const char *string); - -/*! - * \brief Finish a NEW-ENVIRON command. - * - * This must be called after a call to telnet_begin_newenviron() to finish a - * NEW-ENVIRON variable list. - * - * \param telnet Telnet state tracker object. - */ -#define telnet_finish_newenviron(telnet) telnet_finish_sb((telnet)) - -/*! - * \brief Send the TERMINAL-TYPE SEND command. - * - * Sends the sequence IAC TERMINAL-TYPE SEND. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_ttype_send(telnet_t *telnet); - -/*! - * \brief Send the TERMINAL-TYPE IS command. - * - * Sends the sequence IAC TERMINAL-TYPE IS "string". - * - * According to the RFC, the recipient of a TERMINAL-TYPE SEND shall - * send the next possible terminal-type the client supports. Upon sending - * the type, the client should switch modes to begin acting as the terminal - * type is just sent. - * - * The server may continue sending TERMINAL-TYPE IS until it receives a - * terminal type is understands. To indicate to the server that it has - * reached the end of the available optoins, the client must send the last - * terminal type a second time. When the server receives the same terminal - * type twice in a row, it knows it has seen all available terminal types. - * - * After the last terminal type is sent, if the client receives another - * TERMINAL-TYPE SEND command, it must begin enumerating the available - * terminal types from the very beginning. This allows the server to - * scan the available types for a preferred terminal type and, if none - * is found, to then ask the client to switch to an acceptable - * alternative. - * - * Note that if the client only supports a single terminal type, then - * simply sending that one type in response to every SEND will satisfy - * the behavior requirements. - * - * \param telnet Telnet state tracker object. - * \param ttype Name of the terminal-type being sent. - */ -extern void telnet_ttype_is(telnet_t *telnet, const char* ttype); - -/*! - * \brief Send a ZMP command. - * - * \param telnet Telnet state tracker object. - * \param argc Number of ZMP commands being sent. - * \param argv Array of argument strings. - */ -extern void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv); - -/*! - * \brief Send a ZMP command. - * - * Arguments are listed out in var-args style. After the last argument, a - * NULL pointer must be passed in as a sentinel value. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_send_zmpv(telnet_t *telnet, ...) TELNET_GNU_SENTINEL; - -/*! - * \brief Send a ZMP command. - * - * See telnet_send_zmpv(). - */ -extern void telnet_send_vzmpv(telnet_t *telnet, va_list va); - -/*! - * \brief Begin sending a ZMP command - * - * \param telnet Telnet state tracker object. - * \param cmd The first argument (command name) for the ZMP command. - */ -extern void telnet_begin_zmp(telnet_t *telnet, const char *cmd); - -/*! - * \brief Send a ZMP command argument. - * - * \param telnet Telnet state tracker object. - * \param arg Telnet argument string. - */ -extern void telnet_zmp_arg(telnet_t *telnet, const char *arg); - -/*! - * \brief Finish a ZMP command. - * - * This must be called after a call to telnet_begin_zmp() to finish a - * ZMP argument list. - * - * \param telnet Telnet state tracker object. - */ -#define telnet_finish_zmp(telnet) telnet_finish_sb((telnet)) - -/* C++ support */ -#if defined(__cplusplus) -} /* extern "C" */ -#endif - -#endif /* !defined(LIBTELNET_INCLUDE) */ diff --git a/Vendored/libtelnet/Sources/libtelnet.c b/Vendored/libtelnet/Sources/libtelnet.c deleted file mode 100644 index 317fc99..0000000 --- a/Vendored/libtelnet/Sources/libtelnet.c +++ /dev/null @@ -1,1549 +0,0 @@ -/* - * libtelnet - TELNET protocol handling library - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include -#endif -#include -#include -#include -#include -#include -#include - -/* Win32 compatibility */ -#if defined(_WIN32) -# define vsnprintf _vsnprintf -# define __func__ __FUNCTION__ -# define ZLIB_WINAPI 1 -#endif - -#if defined(HAVE_ZLIB) -# include -#endif - -#include "libtelnet.h" - -/* inlinable functions */ -#if defined(__GNUC__) || __STDC_VERSION__ >= 199901L -# define INLINE __inline__ -#else -# define INLINE -#endif - -/* helper for Q-method option tracking */ -#define Q_US(q) ((q).state & 0x0F) -#define Q_HIM(q) (((q).state & 0xF0) >> 4) -#define Q_MAKE(us,him) ((us) | ((him) << 4)) - -/* helper for the negotiation routines */ -#define NEGOTIATE_EVENT(telnet,cmd,opt) \ - ev.type = (cmd); \ - ev.neg.telopt = (opt); \ - (telnet)->eh((telnet), &ev, (telnet)->ud); - -/* telnet state codes */ -enum telnet_state_t { - TELNET_STATE_DATA = 0, - TELNET_STATE_IAC, - TELNET_STATE_WILL, - TELNET_STATE_WONT, - TELNET_STATE_DO, - TELNET_STATE_DONT, - TELNET_STATE_SB, - TELNET_STATE_SB_DATA, - TELNET_STATE_SB_DATA_IAC -}; -typedef enum telnet_state_t telnet_state_t; - -/* telnet state tracker */ -struct telnet_t { - /* user data */ - void *ud; - /* telopt support table */ - const telnet_telopt_t *telopts; - /* event handler */ - telnet_event_handler_t eh; -#if defined(HAVE_ZLIB) - /* zlib (mccp2) compression */ - z_stream *z; -#endif - /* RFC1143 option negotiation states */ - struct telnet_rfc1143_t *q; - /* sub-request buffer */ - char *buffer; - /* current size of the buffer */ - size_t buffer_size; - /* current buffer write position (also length of buffer data) */ - size_t buffer_pos; - /* current state */ - enum telnet_state_t state; - /* option flags */ - unsigned char flags; - /* current subnegotiation telopt */ - unsigned char sb_telopt; - /* length of RFC1143 queue */ - unsigned char q_size; -}; - -/* RFC1143 option negotiation state */ -typedef struct telnet_rfc1143_t { - unsigned char telopt; - unsigned char state; -} telnet_rfc1143_t; - -/* RFC1143 state names */ -#define Q_NO 0 -#define Q_YES 1 -#define Q_WANTNO 2 -#define Q_WANTYES 3 -#define Q_WANTNO_OP 4 -#define Q_WANTYES_OP 5 - -/* buffer sizes */ -static const size_t _buffer_sizes[] = { 0, 512, 2048, 8192, 16384, }; -static const size_t _buffer_sizes_count = sizeof(_buffer_sizes) / - sizeof(_buffer_sizes[0]); - -/* error generation function */ -static telnet_error_t _error(telnet_t *telnet, unsigned line, - const char* func, telnet_error_t err, int fatal, const char *fmt, - ...) { - telnet_event_t ev; - char buffer[512]; - va_list va; - - /* format informational text */ - va_start(va, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, va); - va_end(va); - - /* send error event to the user */ - ev.type = fatal ? TELNET_EV_ERROR : TELNET_EV_WARNING; - ev.error.file = __FILE__; - ev.error.func = func; - ev.error.line = line; - ev.error.msg = buffer; - telnet->eh(telnet, &ev, telnet->ud); - - return err; -} - -#if defined(HAVE_ZLIB) -/* initialize the zlib box for a telnet box; if deflate is non-zero, it - * initializes zlib for delating (compression), otherwise for inflating - * (decompression). returns TELNET_EOK on success, something else on - * failure. - */ -telnet_error_t _init_zlib(telnet_t *telnet, int deflate, int err_fatal) { - z_stream *z; - int rs; - - /* if compression is already enabled, fail loudly */ - if (telnet->z != 0) - return _error(telnet, __LINE__, __func__, TELNET_EBADVAL, - err_fatal, "cannot initialize compression twice"); - - /* allocate zstream box */ - if ((z= (z_stream *)calloc(1, sizeof(z_stream))) == 0) - return _error(telnet, __LINE__, __func__, TELNET_ENOMEM, err_fatal, - "malloc() failed: %s", strerror(errno)); - - /* initialize */ - if (deflate) { - if ((rs = deflateInit(z, Z_DEFAULT_COMPRESSION)) != Z_OK) { - free(z); - return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, - err_fatal, "deflateInit() failed: %s", zError(rs)); - } - telnet->flags |= TELNET_PFLAG_DEFLATE; - } else { - if ((rs = inflateInit(z)) != Z_OK) { - free(z); - return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, - err_fatal, "inflateInit() failed: %s", zError(rs)); - } - telnet->flags &= ~TELNET_PFLAG_DEFLATE; - } - - telnet->z = z; - - return TELNET_EOK; -} -#endif /* defined(HAVE_ZLIB) */ - -/* push bytes out, compressing them first if need be */ -static void _send(telnet_t *telnet, const char *buffer, - size_t size) { - telnet_event_t ev; - -#if defined(HAVE_ZLIB) - /* if we have a deflate (compression) zlib box, use it */ - if (telnet->z != 0 && telnet->flags & TELNET_PFLAG_DEFLATE) { - char deflate_buffer[1024]; - int rs; - - /* initialize z state */ - telnet->z->next_in = (unsigned char *)buffer; - telnet->z->avail_in = (unsigned int)size; - telnet->z->next_out = (unsigned char *)deflate_buffer; - telnet->z->avail_out = sizeof(deflate_buffer); - - /* deflate until buffer exhausted and all output is produced */ - while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) { - /* compress */ - if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK) { - _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1, - "deflate() failed: %s", zError(rs)); - deflateEnd(telnet->z); - free(telnet->z); - telnet->z = 0; - break; - } - - /* send event */ - ev.type = TELNET_EV_SEND; - ev.data.buffer = deflate_buffer; - ev.data.size = sizeof(deflate_buffer) - telnet->z->avail_out; - telnet->eh(telnet, &ev, telnet->ud); - - /* prepare output buffer for next run */ - telnet->z->next_out = (unsigned char *)deflate_buffer; - telnet->z->avail_out = sizeof(deflate_buffer); - } - - /* do not continue with remaining code */ - return; - } -#endif /* defined(HAVE_ZLIB) */ - - ev.type = TELNET_EV_SEND; - ev.data.buffer = buffer; - ev.data.size = size; - telnet->eh(telnet, &ev, telnet->ud); -} - -/* to send bags of unsigned chars */ -#define _sendu(t, d, s) _send((t), (const char*)(d), (s)) - -/* check if we support a particular telopt; if us is non-zero, we - * check if we (local) supports it, otherwise we check if he (remote) - * supports it. return non-zero if supported, zero if not supported. - */ -static INLINE int _check_telopt(telnet_t *telnet, unsigned char telopt, - int us) { - int i; - - /* if we have no telopts table, we obviously don't support it */ - if (telnet->telopts == 0) - return 0; - - /* loop unti found or end marker (us and him both 0) */ - for (i = 0; telnet->telopts[i].telopt != -1; ++i) { - if (telnet->telopts[i].telopt == telopt) { - if (us && telnet->telopts[i].us == TELNET_WILL) - return 1; - else if (!us && telnet->telopts[i].him == TELNET_DO) - return 1; - else - return 0; - } - } - - /* not found, so not supported */ - return 0; -} - -/* retrieve RFC1143 option state */ -static INLINE telnet_rfc1143_t _get_rfc1143(telnet_t *telnet, - unsigned char telopt) { - telnet_rfc1143_t empty; - int i; - - /* search for entry */ - for (i = 0; i != telnet->q_size; ++i) { - if (telnet->q[i].telopt == telopt) { - return telnet->q[i]; - } - } - - /* not found, return empty value */ - empty.telopt = telopt; - empty.state = 0; - return empty; -} - -/* save RFC1143 option state */ -static INLINE void _set_rfc1143(telnet_t *telnet, unsigned char telopt, - char us, char him) { - telnet_rfc1143_t *qtmp; - int i; - - /* search for entry */ - for (i = 0; i != telnet->q_size; ++i) { - if (telnet->q[i].telopt == telopt) { - telnet->q[i].state = Q_MAKE(us,him); - return; - } - } - - /* we're going to need to track state for it, so grow the queue - * by 4 (four) elements and put the telopt into it; bail on allocation - * error. we go by four because it seems like a reasonable guess as - * to the number of enabled options for most simple code, and it - * allows for an acceptable number of reallocations for complex code. - */ - if ((qtmp = (telnet_rfc1143_t *)realloc(telnet->q, - sizeof(telnet_rfc1143_t) * (telnet->q_size + 4))) == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "realloc() failed: %s", strerror(errno)); - return; - } - memset(&qtmp[telnet->q_size], 0, sizeof(telnet_rfc1143_t) * 4); - telnet->q = qtmp; - telnet->q[telnet->q_size].telopt = telopt; - telnet->q[telnet->q_size].state = Q_MAKE(us, him); - telnet->q_size += 4; -} - -/* send negotiation bytes */ -static INLINE void _send_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char telopt) { - unsigned char bytes[3]; - bytes[0] = TELNET_IAC; - bytes[1] = cmd; - bytes[2] = telopt; - _sendu(telnet, bytes, 3); -} - -/* negotiation handling magic for RFC1143 */ -static void _negotiate(telnet_t *telnet, unsigned char telopt) { - telnet_event_t ev; - telnet_rfc1143_t q; - - /* in PROXY mode, just pass it thru and do nothing */ - if (telnet->flags & TELNET_FLAG_PROXY) { - switch ((int)telnet->state) { - case TELNET_STATE_WILL: - NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); - break; - case TELNET_STATE_WONT: - NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); - break; - case TELNET_STATE_DO: - NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); - break; - case TELNET_STATE_DONT: - NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); - break; - } - return; - } - - /* lookup the current state of the option */ - q = _get_rfc1143(telnet, telopt); - - /* start processing... */ - switch ((int)telnet->state) { - /* request to enable option on remote end or confirm DO */ - case TELNET_STATE_WILL: - switch (Q_HIM(q)) { - case Q_NO: - if (_check_telopt(telnet, telopt, 0)) { - _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); - _send_negotiate(telnet, TELNET_DO, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); - } else - _send_negotiate(telnet, TELNET_DONT, telopt); - break; - case Q_WANTNO: - _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); - NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "DONT answered by WILL"); - break; - case Q_WANTNO_OP: - _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); - NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "DONT answered by WILL"); - break; - case Q_WANTYES: - _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); - NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); - break; - case Q_WANTYES_OP: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); - _send_negotiate(telnet, TELNET_DONT, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); - break; - } - break; - - /* request to disable option on remote end, confirm DONT, reject DO */ - case TELNET_STATE_WONT: - switch (Q_HIM(q)) { - case Q_YES: - _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); - _send_negotiate(telnet, TELNET_DONT, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); - break; - case Q_WANTNO: - _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); - NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); - break; - case Q_WANTNO_OP: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); - NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); - break; - case Q_WANTYES: - case Q_WANTYES_OP: - _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); - break; - } - break; - - /* request to enable option on local end or confirm WILL */ - case TELNET_STATE_DO: - switch (Q_US(q)) { - case Q_NO: - if (_check_telopt(telnet, telopt, 1)) { - _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WILL, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); - } else - _send_negotiate(telnet, TELNET_WONT, telopt); - break; - case Q_WANTNO: - _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); - NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "WONT answered by DO"); - break; - case Q_WANTNO_OP: - _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); - NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "WONT answered by DO"); - break; - case Q_WANTYES: - _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); - NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); - break; - case Q_WANTYES_OP: - _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WONT, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); - break; - } - break; - - /* request to disable option on local end, confirm WONT, reject WILL */ - case TELNET_STATE_DONT: - switch (Q_US(q)) { - case Q_YES: - _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WONT, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); - break; - case Q_WANTNO: - _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); - NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); - break; - case Q_WANTNO_OP: - _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WILL, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); - break; - case Q_WANTYES: - case Q_WANTYES_OP: - _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); - break; - } - break; - } -} - -/* process an ENVIRON/NEW-ENVIRON subnegotiation buffer - * - * the algorithm and approach used here is kind of a hack, - * but it reduces the number of memory allocations we have - * to make. - * - * we copy the bytes back into the buffer, starting at the very - * beginning, which makes it easy to handle the ENVIRON ESC - * escape mechanism as well as ensure the variable name and - * value strings are NUL-terminated, all while fitting inside - * of the original buffer. - */ -static int _environ_telnet(telnet_t *telnet, unsigned char type, - char* buffer, size_t size) { - telnet_event_t ev; - struct telnet_environ_t *values = 0; - char *c, *last, *out; - size_t index, count; - - /* if we have no data, just pass it through */ - if (size == 0) { - return 0; - } - - /* first byte must be a valid command */ - if ((unsigned)buffer[0] != TELNET_ENVIRON_SEND && - (unsigned)buffer[0] != TELNET_ENVIRON_IS && - (unsigned)buffer[0] != TELNET_ENVIRON_INFO) { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "telopt %d subneg has invalid command", type); - return 0; - } - - /* store ENVIRON command */ - ev.environ.cmd = buffer[0]; - - /* if we have no arguments, send an event with no data end return */ - if (size == 1) { - /* no list of variables given */ - ev.environ.values = 0; - ev.environ.size = 0; - - /* invoke event with our arguments */ - ev.type = TELNET_EV_ENVIRON; - telnet->eh(telnet, &ev, telnet->ud); - - return 1; - } - - /* very second byte must be VAR or USERVAR, if present */ - if ((unsigned)buffer[1] != TELNET_ENVIRON_VAR && - (unsigned)buffer[1] != TELNET_ENVIRON_USERVAR) { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "telopt %d subneg missing variable type", type); - return 0; - } - - /* ensure last byte is not an escape byte (makes parsing later easier) */ - if ((unsigned)buffer[size - 1] == TELNET_ENVIRON_ESC) { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "telopt %d subneg ends with ESC", type); - return 0; - } - - /* count arguments; each valid entry starts with VAR or USERVAR */ - count = 0; - for (c = buffer + 1; c < buffer + size; ++c) { - if (*c == TELNET_ENVIRON_VAR || *c == TELNET_ENVIRON_USERVAR) { - ++count; - } else if (*c == TELNET_ENVIRON_ESC) { - /* skip the next byte */ - ++c; - } - } - - /* allocate argument array, bail on error */ - if ((values = (struct telnet_environ_t *)calloc(count, - sizeof(struct telnet_environ_t))) == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "calloc() failed: %s", strerror(errno)); - return 0; - } - - /* parse argument array strings */ - out = buffer; - c = buffer + 1; - for (index = 0; index != count; ++index) { - /* remember the variable type (will be VAR or USERVAR) */ - values[index].type = *c++; - - /* scan until we find an end-marker, and buffer up unescaped - * bytes into our buffer */ - last = out; - while (c < buffer + size) { - /* stop at the next variable or at the value */ - if ((unsigned)*c == TELNET_ENVIRON_VAR || - (unsigned)*c == TELNET_ENVIRON_VALUE || - (unsigned)*c == TELNET_ENVIRON_USERVAR) { - break; - } - - /* buffer next byte (taking into account ESC) */ - if (*c == TELNET_ENVIRON_ESC) { - ++c; - } - - *out++ = *c++; - } - *out++ = '\0'; - - /* store the variable name we have just received */ - values[index].var = last; - values[index].value = ""; - - /* if we got a value, find the next end marker and - * store the value; otherwise, store empty string */ - if (c < buffer + size && *c == TELNET_ENVIRON_VALUE) { - ++c; - last = out; - while (c < buffer + size) { - /* stop when we find the start of the next variable */ - if ((unsigned)*c == TELNET_ENVIRON_VAR || - (unsigned)*c == TELNET_ENVIRON_USERVAR) { - break; - } - - /* buffer next byte (taking into account ESC) */ - if (*c == TELNET_ENVIRON_ESC) { - ++c; - } - - *out++ = *c++; - } - *out++ = '\0'; - - /* store the variable value */ - values[index].value = last; - } - } - - /* pass values array and count to event */ - ev.environ.values = values; - ev.environ.size = count; - - /* invoke event with our arguments */ - ev.type = TELNET_EV_ENVIRON; - telnet->eh(telnet, &ev, telnet->ud); - - /* clean up */ - free(values); - return 1; -} - -/* process an MSSP subnegotiation buffer */ -static int _mssp_telnet(telnet_t *telnet, char* buffer, size_t size) { - telnet_event_t ev; - struct telnet_environ_t *values; - char *var = 0; - char *c, *last, *out; - size_t i, count; - unsigned char next_type; - - /* if we have no data, just pass it through */ - if (size == 0) { - return 0; - } - - /* first byte must be a VAR */ - if ((unsigned)buffer[0] != TELNET_MSSP_VAR) { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "MSSP subnegotiation has invalid data"); - return 0; - } - - /* count the arguments, any part that starts with VALUE */ - for (count = 0, i = 0; i != size; ++i) { - if ((unsigned)buffer[i] == TELNET_MSSP_VAL) { - ++count; - } - } - - /* allocate argument array, bail on error */ - if ((values = (struct telnet_environ_t *)calloc(count, - sizeof(struct telnet_environ_t))) == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "calloc() failed: %s", strerror(errno)); - return 0; - } - - ev.mssp.values = values; - ev.mssp.size = count; - - /* allocate strings in argument array */ - out = last = buffer; - next_type = buffer[0]; - for (i = 0, c = buffer + 1; c < buffer + size;) { - /* search for end marker */ - while (c < buffer + size && (unsigned)*c != TELNET_MSSP_VAR && - (unsigned)*c != TELNET_MSSP_VAL) { - *out++ = *c++; - } - *out++ = '\0'; - - /* if it's a variable name, just store the name for now */ - if (next_type == TELNET_MSSP_VAR) { - var = last; - } else if (next_type == TELNET_MSSP_VAL && var != 0) { - values[i].var = var; - values[i].value = last; - ++i; - } else { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "invalid MSSP subnegotiation data"); - free(values); - return 0; - } - - /* remember our next type and increment c for next loop run */ - last = out; - next_type = *c++; - } - - /* invoke event with our arguments */ - ev.type = TELNET_EV_MSSP; - telnet->eh(telnet, &ev, telnet->ud); - - /* clean up */ - free(values); - - return 0; -} - -/* parse ZMP command subnegotiation buffers */ -static int _zmp_telnet(telnet_t *telnet, const char* buffer, size_t size) { - telnet_event_t ev; - char **argv; - const char *c; - size_t i, argc; - - /* make sure this is a valid ZMP buffer */ - if (size == 0 || buffer[size - 1] != 0) { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "incomplete ZMP frame"); - return 0; - } - - /* count arguments */ - for (argc = 0, c = buffer; c != buffer + size; ++argc) - c += strlen(c) + 1; - - /* allocate argument array, bail on error */ - if ((argv = (char **)calloc(argc, sizeof(char *))) == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "calloc() failed: %s", strerror(errno)); - return 0; - } - - /* populate argument array */ - for (i = 0, c = buffer; i != argc; ++i) { - argv[i] = (char *)c; - c += strlen(c) + 1; - } - - /* invoke event with our arguments */ - ev.type = TELNET_EV_ZMP; - ev.zmp.argv = (const char**)argv; - ev.zmp.argc = argc; - telnet->eh(telnet, &ev, telnet->ud); - - /* clean up */ - free(argv); - return 0; -} - -/* parse TERMINAL-TYPE command subnegotiation buffers */ -static int _ttype_telnet(telnet_t *telnet, const char* buffer, size_t size) { - telnet_event_t ev; - - /* make sure request is not empty */ - if (size == 0) { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "incomplete TERMINAL-TYPE request"); - return 0; - } - - /* make sure request has valid command type */ - if (buffer[0] != TELNET_TTYPE_IS && - buffer[0] != TELNET_TTYPE_SEND) { - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "TERMINAL-TYPE request has invalid type"); - return 0; - } - - /* send proper event */ - if (buffer[0] == TELNET_TTYPE_IS) { - char *name; - - /* allocate space for name */ - if ((name = (char *)malloc(size)) == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "malloc() failed: %s", strerror(errno)); - return 0; - } - memcpy(name, buffer + 1, size - 1); - name[size - 1] = '\0'; - - ev.type = TELNET_EV_TTYPE; - ev.ttype.cmd = TELNET_TTYPE_IS; - ev.ttype.name = name; - telnet->eh(telnet, &ev, telnet->ud); - - /* clean up */ - free(name); - } else { - ev.type = TELNET_EV_TTYPE; - ev.ttype.cmd = TELNET_TTYPE_SEND; - ev.ttype.name = 0; - telnet->eh(telnet, &ev, telnet->ud); - } - - return 0; -} - -/* process a subnegotiation buffer; return non-zero if the current buffer - * must be aborted and reprocessed due to COMPRESS2 being activated - */ -static int _subnegotiate(telnet_t *telnet) { - telnet_event_t ev; - - /* standard subnegotiation event */ - ev.type = TELNET_EV_SUBNEGOTIATION; - ev.sub.telopt = telnet->sb_telopt; - ev.sub.buffer = telnet->buffer; - ev.sub.size = telnet->buffer_pos; - telnet->eh(telnet, &ev, telnet->ud); - - switch (telnet->sb_telopt) { -#if defined(HAVE_ZLIB) - /* received COMPRESS2 begin marker, setup our zlib box and - * start handling the compressed stream if it's not already. - */ - case TELNET_TELOPT_COMPRESS2: - if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS2) { - if (_init_zlib(telnet, 0, 1) != TELNET_EOK) - return 0; - - /* notify app that compression was enabled */ - ev.type = TELNET_EV_COMPRESS; - ev.compress.state = 1; - telnet->eh(telnet, &ev, telnet->ud); - return 1; - } - return 0; -#endif /* defined(HAVE_ZLIB) */ - - /* specially handled subnegotiation telopt types */ - case TELNET_TELOPT_ZMP: - return _zmp_telnet(telnet, telnet->buffer, telnet->buffer_pos); - case TELNET_TELOPT_TTYPE: - return _ttype_telnet(telnet, telnet->buffer, telnet->buffer_pos); - case TELNET_TELOPT_ENVIRON: - case TELNET_TELOPT_NEW_ENVIRON: - return _environ_telnet(telnet, telnet->sb_telopt, telnet->buffer, - telnet->buffer_pos); - case TELNET_TELOPT_MSSP: - return _mssp_telnet(telnet, telnet->buffer, telnet->buffer_pos); - default: - return 0; - } -} - -/* initialize a telnet state tracker */ -telnet_t *telnet_init(const telnet_telopt_t *telopts, - telnet_event_handler_t eh, unsigned char flags, void *user_data) { - /* allocate structure */ - struct telnet_t *telnet = (telnet_t*)calloc(1, sizeof(telnet_t)); - if (telnet == 0) - return 0; - - /* initialize data */ - telnet->ud = user_data; - telnet->telopts = telopts; - telnet->eh = eh; - telnet->flags = flags; - - return telnet; -} - -/* free up any memory allocated by a state tracker */ -void telnet_free(telnet_t *telnet) { - /* free sub-request buffer */ - if (telnet->buffer != 0) { - free(telnet->buffer); - telnet->buffer = 0; - telnet->buffer_size = 0; - telnet->buffer_pos = 0; - } - -#if defined(HAVE_ZLIB) - /* free zlib box */ - if (telnet->z != 0) { - if (telnet->flags & TELNET_PFLAG_DEFLATE) - deflateEnd(telnet->z); - else - inflateEnd(telnet->z); - free(telnet->z); - telnet->z = 0; - } -#endif /* defined(HAVE_ZLIB) */ - - /* free RFC1143 queue */ - if (telnet->q) { - free(telnet->q); - telnet->q = 0; - telnet->q_size = 0; - } - - /* free the telnet structure itself */ - free(telnet); -} - -/* push a byte into the telnet buffer */ -static telnet_error_t _buffer_byte(telnet_t *telnet, - unsigned char byte) { - char *new_buffer; - size_t i; - - /* check if we're out of room */ - if (telnet->buffer_pos == telnet->buffer_size) { - /* find the next buffer size */ - for (i = 0; i != _buffer_sizes_count; ++i) { - if (_buffer_sizes[i] == telnet->buffer_size) { - break; - } - } - - /* overflow -- can't grow any more */ - if (i >= _buffer_sizes_count - 1) { - _error(telnet, __LINE__, __func__, TELNET_EOVERFLOW, 0, - "subnegotiation buffer size limit reached"); - return TELNET_EOVERFLOW; - } - - /* (re)allocate buffer */ - new_buffer = (char *)realloc(telnet->buffer, _buffer_sizes[i + 1]); - if (new_buffer == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "realloc() failed"); - return TELNET_ENOMEM; - } - - telnet->buffer = new_buffer; - telnet->buffer_size = _buffer_sizes[i + 1]; - } - - /* push the byte, all set */ - telnet->buffer[telnet->buffer_pos++] = byte; - return TELNET_EOK; -} - -static void _process(telnet_t *telnet, const char *buffer, size_t size) { - telnet_event_t ev; - unsigned char byte; - size_t i, start; - for (i = start = 0; i != size; ++i) { - byte = buffer[i]; - switch (telnet->state) { - /* regular data */ - case TELNET_STATE_DATA: - /* on an IAC byte, pass through all pending bytes and - * switch states */ - if (byte == TELNET_IAC) { - if (i != start) { - ev.type = TELNET_EV_DATA; - ev.data.buffer = buffer + start; - ev.data.size = i - start; - telnet->eh(telnet, &ev, telnet->ud); - } - telnet->state = TELNET_STATE_IAC; - } - break; - - /* IAC command */ - case TELNET_STATE_IAC: - switch (byte) { - /* subnegotiation */ - case TELNET_SB: - telnet->state = TELNET_STATE_SB; - break; - /* negotiation commands */ - case TELNET_WILL: - telnet->state = TELNET_STATE_WILL; - break; - case TELNET_WONT: - telnet->state = TELNET_STATE_WONT; - break; - case TELNET_DO: - telnet->state = TELNET_STATE_DO; - break; - case TELNET_DONT: - telnet->state = TELNET_STATE_DONT; - break; - /* IAC escaping */ - case TELNET_IAC: - /* event */ - ev.type = TELNET_EV_DATA; - ev.data.buffer = (char*)&byte; - ev.data.size = 1; - telnet->eh(telnet, &ev, telnet->ud); - - /* state update */ - start = i + 1; - telnet->state = TELNET_STATE_DATA; - break; - /* some other command */ - default: - /* event */ - ev.type = TELNET_EV_IAC; - ev.iac.cmd = byte; - telnet->eh(telnet, &ev, telnet->ud); - - /* state update */ - start = i + 1; - telnet->state = TELNET_STATE_DATA; - } - break; - - /* negotiation commands */ - case TELNET_STATE_WILL: - case TELNET_STATE_WONT: - case TELNET_STATE_DO: - case TELNET_STATE_DONT: - _negotiate(telnet, byte); - start = i + 1; - telnet->state = TELNET_STATE_DATA; - break; - - /* subnegotiation -- determine subnegotiation telopt */ - case TELNET_STATE_SB: - telnet->sb_telopt = byte; - telnet->buffer_pos = 0; - telnet->state = TELNET_STATE_SB_DATA; - break; - - /* subnegotiation -- buffer bytes until end request */ - case TELNET_STATE_SB_DATA: - /* IAC command in subnegotiation -- either IAC SE or IAC IAC */ - if (byte == TELNET_IAC) { - telnet->state = TELNET_STATE_SB_DATA_IAC; - } else if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS && byte == TELNET_WILL) { - /* In 1998 MCCP used TELOPT 85 and the protocol defined an invalid - * subnegotiation sequence (IAC SB 85 WILL SE) to start compression. - * Subsequently MCCP version 2 was created in 2000 using TELOPT 86 - * and a valid subnegotiation (IAC SB 86 IAC SE). libtelnet for now - * just captures and discards MCCPv1 sequences. - */ - start = i + 2; - telnet->state = TELNET_STATE_DATA; - /* buffer the byte, or bail if we can't */ - } else if (_buffer_byte(telnet, byte) != TELNET_EOK) { - start = i + 1; - telnet->state = TELNET_STATE_DATA; - } - break; - - /* IAC escaping inside a subnegotiation */ - case TELNET_STATE_SB_DATA_IAC: - switch (byte) { - /* end subnegotiation */ - case TELNET_SE: - /* return to default state */ - start = i + 1; - telnet->state = TELNET_STATE_DATA; - - /* process subnegotiation */ - if (_subnegotiate(telnet) != 0) { - /* any remaining bytes in the buffer are compressed. - * we have to re-invoke telnet_recv to get those - * bytes inflated and abort trying to process the - * remaining compressed bytes in the current _process - * buffer argument - */ - telnet_recv(telnet, &buffer[start], size - start); - return; - } - break; - /* escaped IAC byte */ - case TELNET_IAC: - /* push IAC into buffer */ - if (_buffer_byte(telnet, TELNET_IAC) != - TELNET_EOK) { - start = i + 1; - telnet->state = TELNET_STATE_DATA; - } else { - telnet->state = TELNET_STATE_SB_DATA; - } - break; - /* something else -- protocol error. attempt to process - * content in subnegotiation buffer, then evaluate the - * given command as an IAC code. - */ - default: - _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, - "unexpected byte after IAC inside SB: %d", - byte); - - /* enter IAC state */ - start = i + 1; - telnet->state = TELNET_STATE_IAC; - - /* process subnegotiation; see comment in - * TELNET_STATE_SB_DATA_IAC about invoking telnet_recv() - */ - if (_subnegotiate(telnet) != 0) { - telnet_recv(telnet, &buffer[start], size - start); - return; - } else { - /* recursive call to get the current input byte processed - * as a regular IAC command. we could use a goto, but - * that would be gross. - */ - _process(telnet, (char *)&byte, 1); - } - break; - } - break; - } - } - - /* pass through any remaining bytes */ - if (telnet->state == TELNET_STATE_DATA && i != start) { - ev.type = TELNET_EV_DATA; - ev.data.buffer = buffer + start; - ev.data.size = i - start; - telnet->eh(telnet, &ev, telnet->ud); - } -} - -/* push a bytes into the state tracker */ -void telnet_recv(telnet_t *telnet, const char *buffer, - size_t size) { -#if defined(HAVE_ZLIB) - /* if we have an inflate (decompression) zlib stream, use it */ - if (telnet->z != 0 && !(telnet->flags & TELNET_PFLAG_DEFLATE)) { - char inflate_buffer[1024]; - int rs; - - /* initialize zlib state */ - telnet->z->next_in = (unsigned char*)buffer; - telnet->z->avail_in = (unsigned int)size; - telnet->z->next_out = (unsigned char *)inflate_buffer; - telnet->z->avail_out = sizeof(inflate_buffer); - - /* inflate until buffer exhausted and all output is produced */ - while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) { - /* reset output buffer */ - - /* decompress */ - rs = inflate(telnet->z, Z_SYNC_FLUSH); - - /* process the decompressed bytes on success */ - if (rs == Z_OK || rs == Z_STREAM_END) - _process(telnet, inflate_buffer, sizeof(inflate_buffer) - - telnet->z->avail_out); - else - _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1, - "inflate() failed: %s", zError(rs)); - - /* prepare output buffer for next run */ - telnet->z->next_out = (unsigned char *)inflate_buffer; - telnet->z->avail_out = sizeof(inflate_buffer); - - /* on error (or on end of stream) disable further inflation */ - if (rs != Z_OK) { - telnet_event_t ev; - - /* disable compression */ - inflateEnd(telnet->z); - free(telnet->z); - telnet->z = 0; - - /* send event */ - ev.type = TELNET_EV_COMPRESS; - ev.compress.state = 0; - telnet->eh(telnet, &ev, telnet->ud); - - break; - } - } - - /* COMPRESS2 is not negotiated, just process */ - } else -#endif /* defined(HAVE_ZLIB) */ - _process(telnet, buffer, size); -} - -/* send an iac command */ -void telnet_iac(telnet_t *telnet, unsigned char cmd) { - unsigned char bytes[2]; - bytes[0] = TELNET_IAC; - bytes[1] = cmd; - _sendu(telnet, bytes, 2); -} - -/* send negotiation */ -void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char telopt) { - telnet_rfc1143_t q; - - /* if we're in proxy mode, just send it now */ - if (telnet->flags & TELNET_FLAG_PROXY) { - unsigned char bytes[3]; - bytes[0] = TELNET_IAC; - bytes[1] = cmd; - bytes[2] = telopt; - _sendu(telnet, bytes, 3); - return; - } - - /* get current option states */ - q = _get_rfc1143(telnet, telopt); - - switch (cmd) { - /* advertise willingess to support an option */ - case TELNET_WILL: - switch (Q_US(q)) { - case Q_NO: - _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WILL, telopt); - break; - case Q_WANTNO: - _set_rfc1143(telnet, telopt, Q_WANTNO_OP, Q_HIM(q)); - break; - case Q_WANTYES_OP: - _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); - break; - } - break; - - /* force turn-off of locally enabled option */ - case TELNET_WONT: - switch (Q_US(q)) { - case Q_YES: - _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WONT, telopt); - break; - case Q_WANTYES: - _set_rfc1143(telnet, telopt, Q_WANTYES_OP, Q_HIM(q)); - break; - case Q_WANTNO_OP: - _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); - break; - } - break; - - /* ask remote end to enable an option */ - case TELNET_DO: - switch (Q_HIM(q)) { - case Q_NO: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); - _send_negotiate(telnet, TELNET_DO, telopt); - break; - case Q_WANTNO: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO_OP); - break; - case Q_WANTYES_OP: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); - break; - } - break; - - /* demand remote end disable an option */ - case TELNET_DONT: - switch (Q_HIM(q)) { - case Q_YES: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); - _send_negotiate(telnet, TELNET_DONT, telopt); - break; - case Q_WANTYES: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES_OP); - break; - case Q_WANTNO_OP: - _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); - break; - } - break; - } -} - -/* send non-command data (escapes IAC bytes) */ -void telnet_send(telnet_t *telnet, const char *buffer, - size_t size) { - size_t i, l; - - for (l = i = 0; i != size; ++i) { - /* dump prior portion of text, send escaped bytes */ - if (buffer[i] == (char)TELNET_IAC) { - /* dump prior text if any */ - if (i != l) { - _send(telnet, buffer + l, i - l); - } - l = i + 1; - - /* send escape */ - telnet_iac(telnet, TELNET_IAC); - } - } - - /* send whatever portion of buffer is left */ - if (i != l) { - _send(telnet, buffer + l, i - l); - } -} - -/* send subnegotiation header */ -void telnet_begin_sb(telnet_t *telnet, unsigned char telopt) { - unsigned char sb[3]; - sb[0] = TELNET_IAC; - sb[1] = TELNET_SB; - sb[2] = telopt; - _sendu(telnet, sb, 3); -} - - -/* send complete subnegotiation */ -void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, - const char *buffer, size_t size) { - unsigned char bytes[5]; - bytes[0] = TELNET_IAC; - bytes[1] = TELNET_SB; - bytes[2] = telopt; - bytes[3] = TELNET_IAC; - bytes[4] = TELNET_SE; - - _sendu(telnet, bytes, 3); - telnet_send(telnet, buffer, size); - _sendu(telnet, bytes + 3, 2); - -#if defined(HAVE_ZLIB) - /* if we're a proxy and we just sent the COMPRESS2 marker, we must - * make sure all further data is compressed if not already. - */ - if (telnet->flags & TELNET_FLAG_PROXY && - telopt == TELNET_TELOPT_COMPRESS2) { - telnet_event_t ev; - - if (_init_zlib(telnet, 1, 1) != TELNET_EOK) - return; - - /* notify app that compression was enabled */ - ev.type = TELNET_EV_COMPRESS; - ev.compress.state = 1; - telnet->eh(telnet, &ev, telnet->ud); - } -#endif /* defined(HAVE_ZLIB) */ -} - -void telnet_begin_compress2(telnet_t *telnet) { -#if defined(HAVE_ZLIB) - static const unsigned char compress2[] = { TELNET_IAC, TELNET_SB, - TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE }; - - telnet_event_t ev; - - /* attempt to create output stream first, bail if we can't */ - if (_init_zlib(telnet, 1, 0) != TELNET_EOK) - return; - - /* send compression marker. we send directly to the event handler - * instead of passing through _send because _send would result in - * the compress marker itself being compressed. - */ - ev.type = TELNET_EV_SEND; - ev.data.buffer = (const char*)compress2; - ev.data.size = sizeof(compress2); - telnet->eh(telnet, &ev, telnet->ud); - - /* notify app that compression was successfully enabled */ - ev.type = TELNET_EV_COMPRESS; - ev.compress.state = 1; - telnet->eh(telnet, &ev, telnet->ud); -#endif /* defined(HAVE_ZLIB) */ -} - -/* send formatted data with \r and \n translation in addition to IAC IAC */ -int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va) { - static const char CRLF[] = { '\r', '\n' }; - static const char CRNUL[] = { '\r', '\0' }; - char buffer[1024]; - char *output = buffer; - int rs, i, l; - - /* format */ - rs = vsnprintf(buffer, sizeof(buffer), fmt, va); - if (rs >= sizeof(buffer)) { - output = (char*)malloc(rs + 1); - if (output == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "malloc() failed: %s", strerror(errno)); - return -1; - } - rs = vsnprintf(output, rs + 1, fmt, va); - } - - /* send */ - for (l = i = 0; i != rs; ++i) { - /* special characters */ - if (output[i] == (char)TELNET_IAC || output[i] == '\r' || - output[i] == '\n') { - /* dump prior portion of text */ - if (i != l) - _send(telnet, output + l, i - l); - l = i + 1; - - /* IAC -> IAC IAC */ - if (output[i] == (char)TELNET_IAC) - telnet_iac(telnet, TELNET_IAC); - /* automatic translation of \r -> CRNUL */ - else if (output[i] == '\r') - _send(telnet, CRNUL, 2); - /* automatic translation of \n -> CRLF */ - else if (output[i] == '\n') - _send(telnet, CRLF, 2); - } - } - - /* send whatever portion of output is left */ - if (i != l) { - _send(telnet, output + l, i - l); - } - - /* free allocated memory, if any */ - if (output != buffer) { - free(output); - } - - return rs; -} - -/* see telnet_vprintf */ -int telnet_printf(telnet_t *telnet, const char *fmt, ...) { - va_list va; - int rs; - - va_start(va, fmt); - rs = telnet_vprintf(telnet, fmt, va); - va_end(va); - - return rs; -} - -/* send formatted data through telnet_send */ -int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va) { - char buffer[1024]; - char *output = buffer; - int rs; - - /* format; allocate more space if necessary */ - rs = vsnprintf(buffer, sizeof(buffer), fmt, va); - if (rs >= sizeof(buffer)) { - output = (char*)malloc(rs + 1); - if (output == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "malloc() failed: %s", strerror(errno)); - return -1; - } - rs = vsnprintf(output, rs + 1, fmt, va); - } - - /* send out the formatted data */ - telnet_send(telnet, output, rs); - - /* release allocated memory, if any */ - if (output != buffer) { - free(output); - } - - return rs; -} - -/* see telnet_raw_vprintf */ -int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) { - va_list va; - int rs; - - va_start(va, fmt); - rs = telnet_raw_vprintf(telnet, fmt, va); - va_end(va); - - return rs; -} - -/* begin NEW-ENVIRON subnegotation */ -void telnet_begin_newenviron(telnet_t *telnet, unsigned char cmd) { - telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON); - telnet_send(telnet, (const char *)&cmd, 1); -} - -/* send a NEW-ENVIRON value */ -void telnet_newenviron_value(telnet_t *telnet, unsigned char type, - const char *string) { - telnet_send(telnet, (const char*)&type, 1); - - if (string != 0) { - telnet_send(telnet, string, strlen(string)); - } -} - -/* send TERMINAL-TYPE SEND command */ -void telnet_ttype_send(telnet_t *telnet) { - static const unsigned char SEND[] = { TELNET_IAC, TELNET_SB, - TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND, TELNET_IAC, TELNET_SE }; - _sendu(telnet, SEND, sizeof(SEND)); -} - -/* send TERMINAL-TYPE IS command */ -void telnet_ttype_is(telnet_t *telnet, const char* ttype) { - static const unsigned char IS[] = { TELNET_IAC, TELNET_SB, - TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS }; - _sendu(telnet, IS, sizeof(IS)); - _send(telnet, ttype, strlen(ttype)); - telnet_finish_sb(telnet); -} - -/* send ZMP data */ -void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv) { - size_t i; - - /* ZMP header */ - telnet_begin_zmp(telnet, argv[0]); - - /* send out each argument, including trailing NUL byte */ - for (i = 1; i != argc; ++i) - telnet_zmp_arg(telnet, argv[i]); - - /* ZMP footer */ - telnet_finish_zmp(telnet); -} - -/* send ZMP data using varargs */ -void telnet_send_vzmpv(telnet_t *telnet, va_list va) { - const char* arg; - - /* ZMP header */ - telnet_begin_sb(telnet, TELNET_TELOPT_ZMP); - - /* send out each argument, including trailing NUL byte */ - while ((arg = va_arg(va, const char *)) != 0) - telnet_zmp_arg(telnet, arg); - - /* ZMP footer */ - telnet_finish_zmp(telnet); -} - -/* see telnet_send_vzmpv */ -void telnet_send_zmpv(telnet_t *telnet, ...) { - va_list va; - - va_start(va, telnet); - telnet_send_vzmpv(telnet, va); - va_end(va); -} - -/* begin a ZMP command */ -void telnet_begin_zmp(telnet_t *telnet, const char *cmd) { - telnet_begin_sb(telnet, TELNET_TELOPT_ZMP); - telnet_zmp_arg(telnet, cmd); -} - -/* send a ZMP argument */ -void telnet_zmp_arg(telnet_t *telnet, const char* arg) { - telnet_send(telnet, arg, strlen(arg) + 1); -} diff --git a/Vendored/libtelnet/Sources/libtelnet.h b/Vendored/libtelnet/Sources/libtelnet.h deleted file mode 100644 index affcd9d..0000000 --- a/Vendored/libtelnet/Sources/libtelnet.h +++ /dev/null @@ -1,672 +0,0 @@ -/*! - * \brief libtelnet - TELNET protocol handling library - * - * SUMMARY: - * - * libtelnet is a library for handling the TELNET protocol. It includes - * routines for parsing incoming data from a remote peer as well as formatting - * data to send to the remote peer. - * - * libtelnet uses a callback-oriented API, allowing application-specific - * handling of various events. The callback system is also used for buffering - * outgoing protocol data, allowing the application to maintain control over - * the actual socket connection. - * - * Features supported include the full TELNET protocol, Q-method option - * negotiation, ZMP, MCCP2, MSSP, and NEW-ENVIRON. - * - * CONFORMS TO: - * - * RFC854 - http://www.faqs.org/rfcs/rfc854.html - * RFC855 - http://www.faqs.org/rfcs/rfc855.html - * RFC1091 - http://www.faqs.org/rfcs/rfc1091.html - * RFC1143 - http://www.faqs.org/rfcs/rfc1143.html - * RFC1408 - http://www.faqs.org/rfcs/rfc1408.html - * RFC1572 - http://www.faqs.org/rfcs/rfc1572.html - * - * LICENSE: - * - * 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. - * - * \file libtelnet.h - * - * \version 0.21 - * - * \author Sean Middleditch - */ - -#if !defined(LIBTELNET_INCLUDE) -#define LIBTELNET_INCLUDE 1 - -/* standard C headers necessary for the libtelnet API */ -#include - -/* C++ support */ -#if defined(__cplusplus) -extern "C" { -#endif - -/* printf type checking feature in GCC and some other compilers */ -#if __GNUC__ -# define TELNET_GNU_PRINTF(f,a) __attribute__((format(printf, f, a))) /*!< internal helper */ -# define TELNET_GNU_SENTINEL __attribute__((sentinel)) /*!< internal helper */ -#else -# define TELNET_GNU_PRINTF(f,a) /*!< internal helper */ -# define TELNET_GNU_SENTINEL /*!< internal helper */ -#endif - -/*! Telnet state tracker object type. */ -typedef struct telnet_t telnet_t; - -/*! Telnet event object type. */ -typedef union telnet_event_t telnet_event_t; - -/*! Telnet option table element type. */ -typedef struct telnet_telopt_t telnet_telopt_t; - -/*! \name Telnet commands */ -/*@{*/ -/*! Telnet commands and special values. */ -#define TELNET_IAC 255 -#define TELNET_DONT 254 -#define TELNET_DO 253 -#define TELNET_WONT 252 -#define TELNET_WILL 251 -#define TELNET_SB 250 -#define TELNET_GA 249 -#define TELNET_EL 248 -#define TELNET_EC 247 -#define TELNET_AYT 246 -#define TELNET_AO 245 -#define TELNET_IP 244 -#define TELNET_BREAK 243 -#define TELNET_DM 242 -#define TELNET_NOP 241 -#define TELNET_SE 240 -#define TELNET_EOR 239 -#define TELNET_ABORT 238 -#define TELNET_SUSP 237 -#define TELNET_EOF 236 -/*@}*/ - -/*! \name Telnet option values. */ -/*@{*/ -/*! Telnet options. */ -#define TELNET_TELOPT_BINARY 0 -#define TELNET_TELOPT_ECHO 1 -#define TELNET_TELOPT_RCP 2 -#define TELNET_TELOPT_SGA 3 -#define TELNET_TELOPT_NAMS 4 -#define TELNET_TELOPT_STATUS 5 -#define TELNET_TELOPT_TM 6 -#define TELNET_TELOPT_RCTE 7 -#define TELNET_TELOPT_NAOL 8 -#define TELNET_TELOPT_NAOP 9 -#define TELNET_TELOPT_NAOCRD 10 -#define TELNET_TELOPT_NAOHTS 11 -#define TELNET_TELOPT_NAOHTD 12 -#define TELNET_TELOPT_NAOFFD 13 -#define TELNET_TELOPT_NAOVTS 14 -#define TELNET_TELOPT_NAOVTD 15 -#define TELNET_TELOPT_NAOLFD 16 -#define TELNET_TELOPT_XASCII 17 -#define TELNET_TELOPT_LOGOUT 18 -#define TELNET_TELOPT_BM 19 -#define TELNET_TELOPT_DET 20 -#define TELNET_TELOPT_SUPDUP 21 -#define TELNET_TELOPT_SUPDUPOUTPUT 22 -#define TELNET_TELOPT_SNDLOC 23 -#define TELNET_TELOPT_TTYPE 24 -#define TELNET_TELOPT_EOR 25 -#define TELNET_TELOPT_TUID 26 -#define TELNET_TELOPT_OUTMRK 27 -#define TELNET_TELOPT_TTYLOC 28 -#define TELNET_TELOPT_3270REGIME 29 -#define TELNET_TELOPT_X3PAD 30 -#define TELNET_TELOPT_NAWS 31 -#define TELNET_TELOPT_TSPEED 32 -#define TELNET_TELOPT_LFLOW 33 -#define TELNET_TELOPT_LINEMODE 34 -#define TELNET_TELOPT_XDISPLOC 35 -#define TELNET_TELOPT_ENVIRON 36 -#define TELNET_TELOPT_AUTHENTICATION 37 -#define TELNET_TELOPT_ENCRYPT 38 -#define TELNET_TELOPT_NEW_ENVIRON 39 -#define TELNET_TELOPT_MSSP 70 -#define TELNET_TELOPT_COMPRESS 85 -#define TELNET_TELOPT_COMPRESS2 86 -#define TELNET_TELOPT_ZMP 93 -#define TELNET_TELOPT_EXOPL 255 - -#define TELNET_TELOPT_MCCP2 86 -/*@}*/ - -/*! \name Protocol codes for TERMINAL-TYPE commands. */ -/*@{*/ -/*! TERMINAL-TYPE codes. */ -#define TELNET_TTYPE_IS 0 -#define TELNET_TTYPE_SEND 1 -/*@}*/ - -/*! \name Protocol codes for NEW-ENVIRON/ENVIRON commands. */ -/*@{*/ -/*! NEW-ENVIRON/ENVIRON codes. */ -#define TELNET_ENVIRON_IS 0 -#define TELNET_ENVIRON_SEND 1 -#define TELNET_ENVIRON_INFO 2 -#define TELNET_ENVIRON_VAR 0 -#define TELNET_ENVIRON_VALUE 1 -#define TELNET_ENVIRON_ESC 2 -#define TELNET_ENVIRON_USERVAR 3 -/*@}*/ - -/*! \name Protocol codes for MSSP commands. */ -/*@{*/ -/*! MSSP codes. */ -#define TELNET_MSSP_VAR 1 -#define TELNET_MSSP_VAL 2 -/*@}*/ - -/*! \name Telnet state tracker flags. */ -/*@{*/ -/*! Control behavior of telnet state tracker. */ -#define TELNET_FLAG_PROXY (1<<0) - -#define TELNET_PFLAG_DEFLATE (1<<7) -/*@}*/ - -/*! - * error codes - */ -enum telnet_error_t { - TELNET_EOK = 0, /*!< no error */ - TELNET_EBADVAL, /*!< invalid parameter, or API misuse */ - TELNET_ENOMEM, /*!< memory allocation failure */ - TELNET_EOVERFLOW, /*!< data exceeds buffer size */ - TELNET_EPROTOCOL, /*!< invalid sequence of special bytes */ - TELNET_ECOMPRESS /*!< error handling compressed streams */ -}; -typedef enum telnet_error_t telnet_error_t; /*!< Error code type. */ - -/*! - * event codes - */ -enum telnet_event_type_t { - TELNET_EV_DATA = 0, /*!< raw text data has been received */ - TELNET_EV_SEND, /*!< data needs to be sent to the peer */ - TELNET_EV_IAC, /*!< generic IAC code received */ - TELNET_EV_WILL, /*!< WILL option negotiation received */ - TELNET_EV_WONT, /*!< WONT option neogitation received */ - TELNET_EV_DO, /*!< DO option negotiation received */ - TELNET_EV_DONT, /*!< DONT option negotiation received */ - TELNET_EV_SUBNEGOTIATION, /*!< sub-negotiation data received */ - TELNET_EV_COMPRESS, /*!< compression has been enabled */ - TELNET_EV_ZMP, /*!< ZMP command has been received */ - TELNET_EV_TTYPE, /*!< TTYPE command has been received */ - TELNET_EV_ENVIRON, /*!< ENVIRON command has been received */ - TELNET_EV_MSSP, /*!< MSSP command has been received */ - TELNET_EV_WARNING, /*!< recoverable error has occured */ - TELNET_EV_ERROR /*!< non-recoverable error has occured */ -}; -typedef enum telnet_event_type_t telnet_event_type_t; /*!< Telnet event type. */ - -/*! - * environ/MSSP command information - */ -struct telnet_environ_t { - unsigned char type; /*!< either TELNET_ENVIRON_VAR or TELNET_ENVIRON_USERVAR */ - char *var; /*!< name of the variable being set */ - char *value; /*!< value of variable being set; empty string if no value */ -}; - -/*! - * event information - */ -union telnet_event_t { - /*! - * \brief Event type - * - * The type field will determine which of the other event structure fields - * have been filled in. For instance, if the event type is TELNET_EV_ZMP, - * then the zmp event field (and ONLY the zmp event field) will be filled - * in. - */ - enum telnet_event_type_t type; - - /*! - * data event: for DATA and SEND events - */ - struct data_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char *buffer; /*!< byte buffer */ - size_t size; /*!< number of bytes in buffer */ - } data; - - /*! - * WARNING and ERROR events - */ - struct error_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char *file; /*!< file the error occured in */ - const char *func; /*!< function the error occured in */ - const char *msg; /*!< error message string */ - int line; /*!< line of file error occured on */ - telnet_error_t errcode; /*!< error code */ - } error; - - /*! - * command event: for IAC - */ - struct iac_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char cmd; /*!< telnet command received */ - } iac; - - /*! - * negotiation event: WILL, WONT, DO, DONT - */ - struct negotiate_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char telopt; /*!< option being negotiated */ - } neg; - - /*! - * subnegotiation event - */ - struct subnegotiate_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char *buffer; /*!< data of sub-negotiation */ - size_t size; /*!< number of bytes in buffer */ - unsigned char telopt; /*!< option code for negotiation */ - } sub; - - /*! - * ZMP event - */ - struct zmp_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const char **argv; /*!< array of argument string */ - size_t argc; /*!< number of elements in argv */ - } zmp; - - /*! - * TTYPE event - */ - struct ttype_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char cmd; /*!< TELNET_TTYPE_IS or TELNET_TTYPE_SEND */ - const char* name; /*!< terminal type name (IS only) */ - } ttype; - - /*! - * COMPRESS event - */ - struct compress_t { - enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char state; /*!< 1 if compression is enabled, - 0 if disabled */ - } compress; - - /*! - * ENVIRON/NEW-ENVIRON event - */ - struct environ_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const struct telnet_environ_t *values; /*!< array of variable values */ - size_t size; /*!< number of elements in values */ - unsigned char cmd; /*!< SEND, IS, or INFO */ - } environ; - - /*! - * MSSP event - */ - struct mssp_t { - enum telnet_event_type_t _type; /*!< alias for type */ - const struct telnet_environ_t *values; /*!< array of variable values */ - size_t size; /*!< number of elements in values */ - } mssp; -}; - -/*! - * \brief event handler - * - * This is the type of function that must be passed to - * telnet_init() when creating a new telnet object. The - * function will be invoked once for every event generated - * by the libtelnet protocol parser. - * - * \param telnet The telnet object that generated the event - * \param event Event structure with details about the event - * \param user_data User-supplied pointer - */ -typedef void (*telnet_event_handler_t)(telnet_t *telnet, - telnet_event_t *event, void *user_data); - -/*! - * telopt support table element; use telopt of -1 for end marker - */ -struct telnet_telopt_t { - short telopt; /*!< one of the TELOPT codes or -1 */ - unsigned char us; /*!< TELNET_WILL or TELNET_WONT */ - unsigned char him; /*!< TELNET_DO or TELNET_DONT */ -}; - -/*! - * state tracker -- private data structure - */ -struct telnet_t; - -/*! - * \brief Initialize a telnet state tracker. - * - * This function initializes a new state tracker, which is used for all - * other libtelnet functions. Each connection must have its own - * telnet state tracker object. - * - * \param telopts Table of TELNET options the application supports. - * \param eh Event handler function called for every event. - * \param flags 0 or TELNET_FLAG_PROXY. - * \param user_data Optional data pointer that will be passsed to eh. - * \return Telent state tracker object. - */ -extern telnet_t* telnet_init(const telnet_telopt_t *telopts, - telnet_event_handler_t eh, unsigned char flags, void *user_data); - -/*! - * \brief Free up any memory allocated by a state tracker. - * - * This function must be called when a telnet state tracker is no - * longer needed (such as after the connection has been closed) to - * release any memory resources used by the state tracker. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_free(telnet_t *telnet); - -/*! - * \brief Push a byte buffer into the state tracker. - * - * Passes one or more bytes to the telnet state tracker for - * protocol parsing. The byte buffer is most often going to be - * the buffer that recv() was called for while handling the - * connection. - * - * \param telnet Telnet state tracker object. - * \param buffer Pointer to byte buffer. - * \param size Number of bytes pointed to by buffer. - */ -extern void telnet_recv(telnet_t *telnet, const char *buffer, - size_t size); - -/*! - * \brief Send a telnet command. - * - * \param telnet Telnet state tracker object. - * \param cmd Command to send. - */ -extern void telnet_iac(telnet_t *telnet, unsigned char cmd); - -/*! - * \brief Send negotiation command. - * - * Internally, libtelnet uses RFC1143 option negotiation rules. - * The negotiation commands sent with this function may be ignored - * if they are determined to be redundant. - * - * \param telnet Telnet state tracker object. - * \param cmd TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT. - * \param opt One of the TELNET_TELOPT_* values. - */ -extern void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char opt); - -/*! - * Send non-command data (escapes IAC bytes). - * - * \param telnet Telnet state tracker object. - * \param buffer Buffer of bytes to send. - * \param size Number of bytes to send. - */ -extern void telnet_send(telnet_t *telnet, - const char *buffer, size_t size); - -/*! - * \brief Begin a sub-negotiation command. - * - * Sends IAC SB followed by the telopt code. All following data sent - * will be part of the sub-negotiation, until telnet_finish_sb() is - * called. - * - * \param telnet Telnet state tracker object. - * \param telopt One of the TELNET_TELOPT_* values. - */ -extern void telnet_begin_sb(telnet_t *telnet, - unsigned char telopt); - -/*! - * \brief Finish a sub-negotiation command. - * - * This must be called after a call to telnet_begin_sb() to finish a - * sub-negotiation command. - * - * \param telnet Telnet state tracker object. - */ -#define telnet_finish_sb(telnet) telnet_iac((telnet), TELNET_SE) - -/*! - * \brief Shortcut for sending a complete subnegotiation buffer. - * - * Equivalent to: - * telnet_begin_sb(telnet, telopt); - * telnet_send(telnet, buffer, size); - * telnet_finish_sb(telnet); - * - * \param telnet Telnet state tracker format. - * \param telopt One of the TELNET_TELOPT_* values. - * \param buffer Byte buffer for sub-negotiation data. - * \param size Number of bytes to use for sub-negotiation data. - */ -extern void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, - const char *buffer, size_t size); - -/*! - * \brief Begin sending compressed data. - * - * This function will begein sending data using the COMPRESS2 option, - * which enables the use of zlib to compress data sent to the client. - * The client must offer support for COMPRESS2 with option negotiation, - * and zlib support must be compiled into libtelnet. - * - * Only the server may call this command. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_begin_compress2(telnet_t *telnet); - -/*! - * \brief Send formatted data. - * - * This function is a wrapper around telnet_send(). It allows using - * printf-style formatting. - * - * Additionally, this function will translate \\r to the CR NUL construct and - * \\n with CR LF, as well as automatically escaping IAC bytes like - * telnet_send(). - * - * \param telnet Telnet state tracker object. - * \param fmt Format string. - * \return Number of bytes sent. - */ -extern int telnet_printf(telnet_t *telnet, const char *fmt, ...) - TELNET_GNU_PRINTF(2, 3); - -/*! - * \brief Send formatted data. - * - * See telnet_printf(). - */ -extern int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va); - -/*! - * \brief Send formatted data (no newline escaping). - * - * This behaves identically to telnet_printf(), except that the \\r and \\n - * characters are not translated. The IAC byte is still escaped as normal - * with telnet_send(). - * - * \param telnet Telnet state tracker object. - * \param fmt Format string. - * \return Number of bytes sent. - */ -extern int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) - TELNET_GNU_PRINTF(2, 3); - -/*! - * \brief Send formatted data (no newline escaping). - * - * See telnet_raw_printf(). - */ -extern int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va); - -/*! - * \brief Begin a new set of NEW-ENVIRON values to request or send. - * - * This function will begin the sub-negotiation block for sending or - * requesting NEW-ENVIRON values. - * - * The telnet_finish_newenviron() macro must be called after this - * function to terminate the NEW-ENVIRON command. - * - * \param telnet Telnet state tracker object. - * \param type One of TELNET_ENVIRON_SEND, TELNET_ENVIRON_IS, or - * TELNET_ENVIRON_INFO. - */ -extern void telnet_begin_newenviron(telnet_t *telnet, unsigned char type); - -/*! - * \brief Send a NEW-ENVIRON variable name or value. - * - * This can only be called between calls to telnet_begin_newenviron() and - * telnet_finish_newenviron(). - * - * \param telnet Telnet state tracker object. - * \param type One of TELNET_ENVIRON_VAR, TELNET_ENVIRON_USERVAR, or - * TELNET_ENVIRON_VALUE. - * \param string Variable name or value. - */ -extern void telnet_newenviron_value(telnet_t* telnet, unsigned char type, - const char *string); - -/*! - * \brief Finish a NEW-ENVIRON command. - * - * This must be called after a call to telnet_begin_newenviron() to finish a - * NEW-ENVIRON variable list. - * - * \param telnet Telnet state tracker object. - */ -#define telnet_finish_newenviron(telnet) telnet_finish_sb((telnet)) - -/*! - * \brief Send the TERMINAL-TYPE SEND command. - * - * Sends the sequence IAC TERMINAL-TYPE SEND. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_ttype_send(telnet_t *telnet); - -/*! - * \brief Send the TERMINAL-TYPE IS command. - * - * Sends the sequence IAC TERMINAL-TYPE IS "string". - * - * According to the RFC, the recipient of a TERMINAL-TYPE SEND shall - * send the next possible terminal-type the client supports. Upon sending - * the type, the client should switch modes to begin acting as the terminal - * type is just sent. - * - * The server may continue sending TERMINAL-TYPE IS until it receives a - * terminal type is understands. To indicate to the server that it has - * reached the end of the available optoins, the client must send the last - * terminal type a second time. When the server receives the same terminal - * type twice in a row, it knows it has seen all available terminal types. - * - * After the last terminal type is sent, if the client receives another - * TERMINAL-TYPE SEND command, it must begin enumerating the available - * terminal types from the very beginning. This allows the server to - * scan the available types for a preferred terminal type and, if none - * is found, to then ask the client to switch to an acceptable - * alternative. - * - * Note that if the client only supports a single terminal type, then - * simply sending that one type in response to every SEND will satisfy - * the behavior requirements. - * - * \param telnet Telnet state tracker object. - * \param ttype Name of the terminal-type being sent. - */ -extern void telnet_ttype_is(telnet_t *telnet, const char* ttype); - -/*! - * \brief Send a ZMP command. - * - * \param telnet Telnet state tracker object. - * \param argc Number of ZMP commands being sent. - * \param argv Array of argument strings. - */ -extern void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv); - -/*! - * \brief Send a ZMP command. - * - * Arguments are listed out in var-args style. After the last argument, a - * NULL pointer must be passed in as a sentinel value. - * - * \param telnet Telnet state tracker object. - */ -extern void telnet_send_zmpv(telnet_t *telnet, ...) TELNET_GNU_SENTINEL; - -/*! - * \brief Send a ZMP command. - * - * See telnet_send_zmpv(). - */ -extern void telnet_send_vzmpv(telnet_t *telnet, va_list va); - -/*! - * \brief Begin sending a ZMP command - * - * \param telnet Telnet state tracker object. - * \param cmd The first argument (command name) for the ZMP command. - */ -extern void telnet_begin_zmp(telnet_t *telnet, const char *cmd); - -/*! - * \brief Send a ZMP command argument. - * - * \param telnet Telnet state tracker object. - * \param arg Telnet argument string. - */ -extern void telnet_zmp_arg(telnet_t *telnet, const char *arg); - -/*! - * \brief Finish a ZMP command. - * - * This must be called after a call to telnet_begin_zmp() to finish a - * ZMP argument list. - * - * \param telnet Telnet state tracker object. - */ -#define telnet_finish_zmp(telnet) telnet_finish_sb((telnet)) - -/* C++ support */ -#if defined(__cplusplus) -} /* extern "C" */ -#endif - -#endif /* !defined(LIBTELNET_INCLUDE) */ diff --git a/src/MRTests/MRSocketTests.m b/src/MRTests/MRSocketTests.m index a3b882a..2038606 100644 --- a/src/MRTests/MRSocketTests.m +++ b/src/MRTests/MRSocketTests.m @@ -8,7 +8,6 @@ #import "MRTestHelpers.h" #import "SSMUDSocket.h" -#import "SPLTelnetLib.h" @interface MRSocketTests : XCTestCase @@ -47,12 +46,10 @@ - (void)testSocketInitialSetup { expect(sut.delegate).to.equal(mockSocketDelegate); } -- (void)testSocketConnectSendsConnectAndSSLCheck { +- (void)testSocketConnectSendsConnect { [[mockSocketDelegate expect] mudsocketDidConnectToHost:sut]; - [[mockSocketDelegate expect] mudsocketShouldAttemptSSL:sut]; [sut socket:socket didConnectToHost:@"nanvaent.org" port:23]; - [sut socket:socket didReadData:[@"Hello" dataUsingEncoding:NSUTF8StringEncoding] withTag:0]; [mockSocketDelegate verifyWithDelay:3]; } @@ -79,21 +76,4 @@ - (void)testSocketParsesAttributedLines { [mockSocketDelegate verifyWithDelay:3]; } -#pragma mark - GMCP - -- (void)testSocketForwardsGMCPToDelegate { - // SSMUDSocket conforms to SPLTelnetLibDelegate, so we can call the method directly - id telnetDelegate = (id)sut; - - NSDictionary *testData = @{ @"hp": @50, @"maxhp": @100 }; - - [[mockSocketDelegate expect] mudsocket:sut - receivedGMCPModule:@"char.vitals" - data:testData]; - - [telnetDelegate telnetLibrary:nil receivedGMCPModule:@"char.vitals" data:testData]; - - [mockSocketDelegate verifyWithDelay:1]; -} - @end diff --git a/src/MRTests/MRTelnetTests.m b/src/MRTests/MRTelnetTests.m deleted file mode 100644 index be27f2d..0000000 --- a/src/MRTests/MRTelnetTests.m +++ /dev/null @@ -1,246 +0,0 @@ -// -// MRTelnetTests.m -// Mudrammer -// -// Created by Jonathan Hersh on 6/5/15. -// Copyright (c) 2015 splinesoft LLC. All rights reserved. -// - -#import "MRTestHelpers.h" -#import "SPLTelnetLib.h" -#import "SSStringCoder.h" -#import "SSMRConstants.h" -#import "libtelnet.h" - -@interface MRTelnetTests : XCTestCase - -@end - -@implementation MRTelnetTests -{ - SPLTelnetLib *sut; - OCMockObject *stringCoderMock; - OCMockObject *telnetDelegateMock; -} - -- (void)setUp { - [super setUp]; - - stringCoderMock = OCMClassMock([SSStringCoder class]); - telnetDelegateMock = OCMProtocolMock(@protocol(SPLTelnetLibDelegate)); - sut = [[SPLTelnetLib alloc] initWithStringCoder:(SSStringCoder *)stringCoderMock]; - sut.delegate = (id )telnetDelegateMock; - - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:kPrefSimpleTelnetMode]; -} - -- (void)tearDown { - sut.delegate = nil; - sut = nil; - - [super tearDown]; -} - -- (void)testEchoesTextByDefault { - expect(sut.shouldEchoText).to.beTruthy(); -} - -- (void)testConnectingSendsInitialOptions { - for (NSUInteger i = 0; i < 3; i++) { - [[telnetDelegateMock expect] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - } - - [sut socketDidConnect]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testReceivingDataParsesToString { - NSString *testStr = @"Hello World"; - - [[telnetDelegateMock expect] telnetLibrary:sut shouldPrintString:testStr]; - OCMStub([(SSStringCoder *)stringCoderMock stringByDecodingDataWithCurrentEncoding:OCMOCK_ANY]).andReturn(testStr); - - [sut receivedSocketData:[testStr dataUsingEncoding:NSUTF8StringEncoding]]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testSendingCommandsForwardsDataToSocket { - NSString *testStr = @"Yo Dawg"; - NSData *testData = [testStr dataUsingEncoding:NSASCIIStringEncoding]; - - [[telnetDelegateMock expect] telnetLibrary:sut mustSendData:testData]; - OCMStub([(SSStringCoder *)stringCoderMock dataForUserCommands:OCMOCK_ANY]).andReturn(testData); - - [sut sendUserCommands:@[ testStr ]]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testNAWSSendsSocketData { - [[telnetDelegateMock expect] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - - [sut sendNAWSWithSize:CGSizeMake(80, 80)]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testReceivingEchoCommandChangesEchoStatus { - expect(sut.shouldEchoText).to.beTruthy(); - - [[telnetDelegateMock reject] telnetLibrary:sut shouldPrintString:OCMOCK_ANY]; - - unsigned char onBytes[] = { TELNET_IAC, TELNET_WILL, TELNET_TELOPT_ECHO }; - NSData *onData = [NSData dataWithBytes:onBytes length:3]; - - [sut receivedSocketData:onData]; - - expect(sut.shouldEchoText).to.beFalsy(); - - unsigned char offBytes[] = { TELNET_IAC, TELNET_WONT, TELNET_TELOPT_ECHO }; - NSData *offData = [NSData dataWithBytes:offBytes length:3]; - - [sut receivedSocketData:offData]; - - expect(sut.shouldEchoText).to.beTruthy(); - [telnetDelegateMock verify]; -} - -- (void)testReceivingTTYPECommandSendsTTYPE { - unsigned char ttBytes[] = { TELNET_IAC, TELNET_DO, TELNET_TELOPT_TTYPE }; - NSData *ttData = [NSData dataWithBytes:ttBytes length:3]; - - [[telnetDelegateMock expect] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - [[telnetDelegateMock reject] telnetLibrary:sut shouldPrintString:OCMOCK_ANY]; - - [sut receivedSocketData:ttData]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -#pragma mark - GMCP - -#define TELNET_TELOPT_GMCP 201 - -- (void)testGMCPNegotiationRespondsToWill { - // Server sends IAC WILL GMCP, client should respond with IAC DO GMCP - [[telnetDelegateMock expect] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - [[telnetDelegateMock reject] telnetLibrary:sut shouldPrintString:OCMOCK_ANY]; - - unsigned char willGMCP[] = { TELNET_IAC, TELNET_WILL, TELNET_TELOPT_GMCP }; - [sut receivedSocketData:[NSData dataWithBytes:willGMCP length:3]]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testGMCPSubnegotiationWithJSON { - // Server sends IAC SB 201 "char.vitals {"hp":50,"maxhp":100}" IAC SE - NSString *payload = @"char.vitals {\"hp\":50,\"maxhp\":100}"; - NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; - - NSMutableData *packet = [NSMutableData data]; - unsigned char header[] = { TELNET_IAC, TELNET_SB, TELNET_TELOPT_GMCP }; - unsigned char footer[] = { TELNET_IAC, TELNET_SE }; - [packet appendBytes:header length:3]; - [packet appendData:payloadData]; - [packet appendBytes:footer length:2]; - - // Must first negotiate GMCP so libtelnet knows to accept subneg - unsigned char willGMCP[] = { TELNET_IAC, TELNET_WILL, TELNET_TELOPT_GMCP }; - // Allow the DO response - [[telnetDelegateMock stub] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - [sut receivedSocketData:[NSData dataWithBytes:willGMCP length:3]]; - - [[telnetDelegateMock expect] telnetLibrary:sut - receivedGMCPModule:@"char.vitals" - data:[OCMArg checkWithBlock:^BOOL(NSDictionary *data) { - return [data[@"hp"] intValue] == 50 && [data[@"maxhp"] intValue] == 100; - }]]; - - [sut receivedSocketData:packet]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testGMCPSubnegotiationWithoutPayload { - // Some GMCP modules have no JSON payload (just a module name) - NSString *payload = @"Core.Goodbye"; - NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; - - NSMutableData *packet = [NSMutableData data]; - unsigned char header[] = { TELNET_IAC, TELNET_SB, TELNET_TELOPT_GMCP }; - unsigned char footer[] = { TELNET_IAC, TELNET_SE }; - [packet appendBytes:header length:3]; - [packet appendData:payloadData]; - [packet appendBytes:footer length:2]; - - unsigned char willGMCP[] = { TELNET_IAC, TELNET_WILL, TELNET_TELOPT_GMCP }; - [[telnetDelegateMock stub] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - [sut receivedSocketData:[NSData dataWithBytes:willGMCP length:3]]; - - [[telnetDelegateMock expect] telnetLibrary:sut - receivedGMCPModule:@"Core.Goodbye" - data:[OCMArg checkWithBlock:^BOOL(NSDictionary *data) { - return [data count] == 0; - }]]; - - [sut receivedSocketData:packet]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testGMCPSubnegotiationWithInvalidJSON { - // Invalid JSON should still call delegate with the module name and empty dict - NSString *payload = @"char.vitals {not valid json}"; - NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; - - NSMutableData *packet = [NSMutableData data]; - unsigned char header[] = { TELNET_IAC, TELNET_SB, TELNET_TELOPT_GMCP }; - unsigned char footer[] = { TELNET_IAC, TELNET_SE }; - [packet appendBytes:header length:3]; - [packet appendData:payloadData]; - [packet appendBytes:footer length:2]; - - unsigned char willGMCP[] = { TELNET_IAC, TELNET_WILL, TELNET_TELOPT_GMCP }; - [[telnetDelegateMock stub] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - [sut receivedSocketData:[NSData dataWithBytes:willGMCP length:3]]; - - [[telnetDelegateMock expect] telnetLibrary:sut - receivedGMCPModule:@"char.vitals" - data:[OCMArg checkWithBlock:^BOOL(NSDictionary *data) { - return [data count] == 0; - }]]; - - [sut receivedSocketData:packet]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -- (void)testGMCPDoesNotPrintToTerminal { - // GMCP data should never appear as printed text - NSString *payload = @"room.info {\"num\":3001}"; - NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; - - NSMutableData *packet = [NSMutableData data]; - unsigned char header[] = { TELNET_IAC, TELNET_SB, TELNET_TELOPT_GMCP }; - unsigned char footer[] = { TELNET_IAC, TELNET_SE }; - [packet appendBytes:header length:3]; - [packet appendData:payloadData]; - [packet appendBytes:footer length:2]; - - unsigned char willGMCP[] = { TELNET_IAC, TELNET_WILL, TELNET_TELOPT_GMCP }; - [[telnetDelegateMock stub] telnetLibrary:sut mustSendData:OCMOCK_ANY]; - [sut receivedSocketData:[NSData dataWithBytes:willGMCP length:3]]; - - [[telnetDelegateMock reject] telnetLibrary:sut shouldPrintString:OCMOCK_ANY]; - [[telnetDelegateMock expect] telnetLibrary:sut - receivedGMCPModule:OCMOCK_ANY - data:OCMOCK_ANY]; - - [sut receivedSocketData:packet]; - - [telnetDelegateMock verifyWithDelay:1]; -} - -@end diff --git a/src/Mudrammer/Additions/UIColor+SPLANSI.h b/src/Mudrammer/Additions/UIColor+SPLANSI.h index 79c2c01..fa792cd 100644 --- a/src/Mudrammer/Additions/UIColor+SPLANSI.h +++ b/src/Mudrammer/Additions/UIColor+SPLANSI.h @@ -40,6 +40,7 @@ typedef NS_ENUM(NSInteger, SPLSGRCode) { SPLSGRCodeUnderlineSingle = 4, SPLSGRCodeXTermMarker2 = 5, // xterm-256 + SPLSGRCodeXTermMarker2TrueColor = 2, // truecolor (24-bit) SPLSGRCodeReverse = 7, SPLSGRCodeHidden = 8, diff --git a/src/Mudrammer/Network/SPLTelnetLib.h b/src/Mudrammer/Network/SPLTelnetLib.h deleted file mode 100644 index 6a1361d..0000000 --- a/src/Mudrammer/Network/SPLTelnetLib.h +++ /dev/null @@ -1,114 +0,0 @@ -// -// SPLTelnetLib.h -// Mudrammer -// -// Created by Jonathan Hersh on 11/23/14. -// Copyright (c) 2014 Jonathan Hersh. All rights reserved. -// - -@import Foundation; - -@protocol SPLTelnetLibDelegate; -@class SSStringCoder; - -/** - * SPLTelnetLib encompasses a complete telnet implementation, including - * data compression/decompression (via zlib), options negotiations, - * plus various MUD-specific protocols (MSSP). - * - * ALL OUTBOUND DATA SENT TO THE SERVER MUST FIRST PASS THROUGH SPLTelnetLib - * ALL DATA RECEIVED FROM THE SERVER MUST FIRST PASS THROUGH SPLTelnetLib - */ -@interface SPLTelnetLib : NSObject - -- (instancetype) init NS_UNAVAILABLE; - -- (instancetype) initWithStringCoder:(SSStringCoder *)stringCoder NS_DESIGNATED_INITIALIZER; - -@property (nonatomic, weak) id delegate; - -@property (nonatomic, strong, readonly) SSStringCoder *stringCoder; - -/** - * Whether the client should echo text. YES by default. - */ -@property (nonatomic, assign, readonly) BOOL shouldEchoText; - -/** - * The socket successfully connected. We should send some commands - * that indicate our capabilities (NAWS, TTYPE, etc). - */ -- (void) socketDidConnect; - -/** - * When the socket receives data, it must pass that data to the telnet lib - * before performing any other processing. The telnet lib will call delegate - * methods as appropriate. - * - * @param data data received - */ -- (void) receivedSocketData:(NSData *)data; - -/** - * Convert some user commands to data and then send them. - */ -- (void) sendUserCommands:(NSArray *)commands; - -/** - * The socket should send a NAWS message with the specified size. - * - * @param size size to send - */ -- (void) sendNAWSWithSize:(CGSize)size; - -@end - -/** - * All delegate methods are called on the main thread. - */ -@protocol SPLTelnetLibDelegate - -@required -/** - * The telnet library has created some data that must be sent over the socket. - * - * @param library the telnet library - * @param data data to send - */ -- (void) telnetLibrary:(SPLTelnetLib *)library mustSendData:(NSData *)data; - -@optional -/** - * The telnet library has received some data and parsed it into a string that should be printed to the user. - * - * @param library the telnet library - * @param string string to print - */ -- (void) telnetLibrary:(SPLTelnetLib *)library shouldPrintString:(NSString *)string; - -/** - * The telnet library has encountered an unrecoverable error. The socket must disconnect immediately. - * - * @param library the telnet library - * @param error a string describing the fatal error - */ -- (void) telnetLibrary:(SPLTelnetLib *)library encounteredFatalError:(NSString *)error; - -/** - * The telnet library has received some MSSP data and parsed it into a dictionary. - * - * @param library the telnet library - * @param MSSPData dictionary of MSSP data - */ -- (void) telnetLibrary:(SPLTelnetLib *)library receivedMSSPData:(NSDictionary *)MSSPData; - -/** - * The telnet library has received a GMCP subnegotiation and parsed it. - * - * @param library the telnet library - * @param module GMCP module name (e.g. "char.vitals", "Client.Media.Play") - * @param data parsed JSON payload as a dictionary, or empty dictionary if no payload - */ -- (void) telnetLibrary:(SPLTelnetLib *)library receivedGMCPModule:(NSString *)module data:(NSDictionary *)data; - -@end diff --git a/src/Mudrammer/Network/SPLTelnetLib.m b/src/Mudrammer/Network/SPLTelnetLib.m deleted file mode 100644 index 7061bc3..0000000 --- a/src/Mudrammer/Network/SPLTelnetLib.m +++ /dev/null @@ -1,378 +0,0 @@ -// -// SPLTelnetLib.m -// Mudrammer -// -// Created by Jonathan Hersh on 11/23/14. -// Copyright (c) 2014 Jonathan Hersh. All rights reserved. -// - -#import "SPLTelnetLib.h" -@import libtelnet; -#import "SSStringCoder.h" -#import "NSData+SPLDataParsing.h" - -#define TELNET_TELOPT_GMCP 201 - -typedef struct telnet_t * telnet_t_p; - -@interface SPLTelnetLib () - -@property (nonatomic) telnet_t_p spl_telnet_t; - -@property (nonatomic, assign, readwrite) BOOL shouldEchoText; - -#pragma mark - TTYPE - -@property (nonatomic, assign) NSUInteger ttypeIndex; - -/** - * Iterate through our known terminal types and send the next one. - */ -- (void) sendTTYPE; - -@end - -static const telnet_telopt_t SPLTelOpts[] = { - { TELNET_TELOPT_ECHO, TELNET_WILL, TELNET_DO }, - { TELNET_TELOPT_SGA, TELNET_WILL, TELNET_DO }, - { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DO }, - { TELNET_TELOPT_COMPRESS, TELNET_WONT, TELNET_DONT }, - { TELNET_TELOPT_COMPRESS2, TELNET_WILL, TELNET_DO }, - { TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DONT }, - { TELNET_TELOPT_MSSP, TELNET_WILL, TELNET_DO }, - { TELNET_TELOPT_BINARY, TELNET_WONT, TELNET_DONT }, - { TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DO }, - { TELNET_TELOPT_GMCP, TELNET_WONT, TELNET_DO }, - { -1, 0, 0 } -}; - -CG_INLINE void SPLTelnetEventHandler(telnet_t *telnet, - telnet_event_t *ev, - void *user_data) { - - SPLTelnetLib *lib = (__bridge SPLTelnetLib *)user_data; - id delegate = lib.delegate; - - switch (ev->type) { - - /* data received */ - case TELNET_EV_DATA: - - if ([delegate respondsToSelector:@selector(telnetLibrary:shouldPrintString:)]) { - NSData *data = [NSData dataWithBytes:ev->data.buffer length:ev->data.size]; - NSString *string = [lib.stringCoder stringByDecodingDataWithCurrentEncoding:data]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [delegate telnetLibrary:lib - shouldPrintString:string]; - }); - } - - break; - - /* data must be sent */ - case TELNET_EV_SEND: - - if ([delegate respondsToSelector:@selector(telnetLibrary:mustSendData:)]) { - NSData *data = [NSData dataWithBytes:ev->data.buffer - length:ev->data.size]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [delegate telnetLibrary:lib - mustSendData:data]; - }); - } - - break; - - case TELNET_EV_IAC: - - DLog(@"IAC %@", @(ev->iac.cmd)); - - break; - - /* request to enable remote feature (or receipt) */ - case TELNET_EV_WILL: { - - unsigned char telopt = ev->neg.telopt; - DLog(@"WILL %@", @(telopt)); - - /* we'll agree to turn off our echo if server wants us to stop */ - if (telopt == TELNET_TELOPT_ECHO) { - DLog(@"DISABLING ECHO"); - lib.shouldEchoText = NO; - } - - break; - } - - /* notification of disabling remote feature (or receipt) */ - case TELNET_EV_WONT: { - - unsigned char telopt = ev->neg.telopt; - DLog(@"WONT %@", @(telopt)); - - if (telopt == TELNET_TELOPT_ECHO) { - DLog(@"ENABLING ECHO"); - lib.shouldEchoText = YES; - } - - break; - } - /* request to enable local feature (or receipt) */ - case TELNET_EV_DO: { - - unsigned char telopt = ev->neg.telopt; - DLog(@"DO %@", @(telopt)); - - if (telopt == TELNET_TELOPT_TTYPE) { - [lib sendTTYPE]; - } - - break; - } - - /* demand to disable local feature (or receipt) */ - case TELNET_EV_DONT: - - DLog(@"DONT %@", @(ev->neg.telopt)); - - break; - - /* respond to TTYPE commands */ - case TELNET_EV_TTYPE: - - /* respond with our terminal type, if requested */ - if (ev->ttype.cmd == TELNET_TTYPE_SEND) { - [lib sendTTYPE]; - } - - break; - - /* respond to particular subnegotiations */ - case TELNET_EV_SUBNEGOTIATION: - - if (ev->sub.telopt == TELNET_TELOPT_GMCP && ev->sub.size > 0) { - NSData *subData = [NSData dataWithBytes:ev->sub.buffer length:ev->sub.size]; - NSString *subString = [[NSString alloc] initWithData:subData encoding:NSUTF8StringEncoding]; - if (subString) { - NSRange spaceRange = [subString rangeOfString:@" "]; - NSString *module; - NSDictionary *payload = nil; - if (spaceRange.location != NSNotFound) { - module = [subString substringToIndex:spaceRange.location]; - NSString *jsonString = [subString substringFromIndex:spaceRange.location + 1]; - NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - if (jsonData) { - payload = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; - } - } else { - module = subString; - } - if ([delegate respondsToSelector:@selector(telnetLibrary:receivedGMCPModule:data:)]) { - NSDictionary *safePayload = payload ?: @{}; - dispatch_async(dispatch_get_main_queue(), ^{ - [delegate telnetLibrary:lib receivedGMCPModule:module data:safePayload]; - }); - } - } - } else { - DLog(@"SUB %@", @(ev->sub.telopt)); - } - - break; - - case TELNET_EV_COMPRESS: - - DLog(@"COMPRESS ENABLED"); - - break; - - /* error */ - case TELNET_EV_ERROR: - - if ([delegate respondsToSelector:@selector(telnetLibrary:encounteredFatalError:)]) { - NSString *errMsg = @(ev->error.msg); - - dispatch_async(dispatch_get_main_queue(), ^{ - [delegate telnetLibrary:lib - encounteredFatalError:errMsg]; - }); - } - - break; - - case TELNET_EV_ENVIRON: - - DLog(@"ENVIRON"); - - break; - - case TELNET_EV_MSSP: { - - NSMutableDictionary *MSSPDict = [NSMutableDictionary dictionaryWithCapacity:ev->mssp.size]; - - const struct telnet_environ_t *MSSPValues = ev->mssp.values; - - for (NSUInteger i = 0; i < ev->mssp.size; i++) { - - struct telnet_environ_t MSSPValue = *MSSPValues; - - if (MSSPValue.var == NULL || MSSPValue.value == NULL) { - continue; - } - - NSString *key = @(MSSPValue.var); - NSString *value = @(MSSPValue.value); - - if ([key length] > 0 && [value length] > 0) { - if ([MSSPDict[key] isKindOfClass:[NSArray class]]) { - MSSPDict[key] = [MSSPDict[key] arrayByAddingObject:value]; - } else if ([MSSPDict[key] isKindOfClass:[NSString class]]) { - MSSPDict[key] = @[ MSSPDict[key], value ]; - } else { - MSSPDict[key] = value; - } - } - - MSSPValues++; - } - - if ([delegate respondsToSelector:@selector(telnetLibrary:receivedMSSPData:)]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [delegate telnetLibrary:lib - receivedMSSPData:MSSPDict]; - }); - } - - break; - } - case TELNET_EV_WARNING: - - DLog(@"WARNING %@ file %@ func %@ line %@", - @(ev->error.msg), - @(ev->error.file), - @(ev->error.func), - @(ev->error.line)); - - break; - - case TELNET_EV_ZMP: - - DLog(@"ZMP"); - - break; - } -} - -@implementation SPLTelnetLib - -- (instancetype)initWithStringCoder:(SSStringCoder *)stringCoder { - if ((self = [super init])) { - _shouldEchoText = YES; - _ttypeIndex = 0; - _spl_telnet_t = telnet_init(SPLTelOpts, SPLTelnetEventHandler, 0, (__bridge void *)self); - _stringCoder = stringCoder; - } - - return self; -} - -- (void)dealloc { - telnet_free(_spl_telnet_t); -} - -#pragma mark - Telnet Options - -- (BOOL)isSimpleTelnetMode { - return [[NSUserDefaults standardUserDefaults] boolForKey:kPrefSimpleTelnetMode]; -} - -- (void)sendTTYPE { - if ([self isSimpleTelnetMode]) { - DLog(@"NO TTYPE - Simple"); - return; - } - - // IAC SB TERMINAL-TYPE IS IBM-3278-2 IAC SE - static NSArray *ttypes; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ttypes = @[ - @"WAMMER", - @"XTERM", - @"ANSI", - ]; - }); - - // When we send the last TTYPE in our list, we should send the last item once more. - // Any subsequent requests for TTYPE should go back to the first item in the list. - NSString *type; - - if (self.ttypeIndex < [ttypes count]) { - type = ttypes[self.ttypeIndex]; - self.ttypeIndex++; - } else if(self.ttypeIndex == [ttypes count]) { - type = [ttypes lastObject]; - self.ttypeIndex++; - } else { - type = [ttypes firstObject]; - self.ttypeIndex = 1; - } - - DLog(@"SENDING TTYPE %@", type); - - telnet_ttype_is(self.spl_telnet_t, [type cStringUsingEncoding:NSASCIIStringEncoding]); -} - -- (void)sendNAWSWithSize:(CGSize)size { - if ([self isSimpleTelnetMode]) { - DLog(@"NO NAWS - Simple"); - return; - } - - DLog(@"SEND NAWS %@", NSStringFromCGSize(size)); - - // IAC SB NAWS 0 80 0 24 IAC SE - NSUInteger width = (NSUInteger)size.width; - - if (width >= UINT8_MAX) { - width = UINT8_MAX - 1; - } - - NSUInteger height = (NSUInteger)size.height; - - if (height >= UINT8_MAX) { - height = UINT8_MAX - 1; - } - - char message[4] = { 0, (char) width, 0, (char) height }; - - telnet_subnegotiation(self.spl_telnet_t, TELNET_TELOPT_NAWS, message, 4); -} - -#pragma mark - Telnet Events - -- (void)socketDidConnect { - if ([self isSimpleTelnetMode]) { - DLog(@"NO CONNECT OPTS - SIMPLE TELNET"); - return; - } - - DLog(@"SENDING CONNECT OPTS"); - - telnet_negotiate(self.spl_telnet_t, TELNET_WILL, TELNET_TELOPT_TTYPE); - telnet_negotiate(self.spl_telnet_t, TELNET_DO, TELNET_TELOPT_SGA); - telnet_negotiate(self.spl_telnet_t, TELNET_WILL, TELNET_TELOPT_NAWS); -} - -- (void)receivedSocketData:(NSData *)data { - telnet_recv(self.spl_telnet_t, [data bytes], [data length]); -} - -- (void)sendUserCommands:(NSArray *)commands { - NSData *commandData = [self.stringCoder dataForUserCommands:commands]; - telnet_send(self.spl_telnet_t, [commandData bytes], [commandData length]); -} - -@end diff --git a/src/Mudrammer/Network/SSANSIEngine.m b/src/Mudrammer/Network/SSANSIEngine.m index 2d43405..ba02a99 100644 --- a/src/Mudrammer/Network/SSANSIEngine.m +++ b/src/Mudrammer/Network/SSANSIEngine.m @@ -21,6 +21,10 @@ CG_INLINE BOOL SPLCodesAreXTermSequence(NSInteger code1, NSInteger code2) { return (code2 == SPLSGRCodeXTermMarker2 && (code1 == SPLSGRCodeXTermForeground || code1 == SPLSGRCodeXTermBackground)); } +CG_INLINE BOOL SPLCodesAreTrueColorSequence(NSInteger code1, NSInteger code2) { + return (code2 == SPLSGRCodeXTermMarker2TrueColor && (code1 == SPLSGRCodeXTermForeground || code1 == SPLSGRCodeXTermBackground)); +} + CG_INLINE SPLSGRCode SPLIntenseColorForColor(SPLSGRCode color) { switch (color) { case SPLSGRCodeFgBlack: @@ -230,20 +234,44 @@ - (void)parseColorForColorCodes:(NSArray *)codes options:(SPLTextOptions *)optio BOOL didColor = NO; if ([codes count] > 2) { - for (NSUInteger index = 0; index < [codes count]; index += 3) { - if (index + 2 >= [codes count]) { - break; - } - + NSUInteger index = 0; + while (index + 2 < [codes count]) { NSInteger code1 = [codes[index] integerValue]; NSInteger code2 = [codes[index + 1] integerValue]; NSInteger code3 = [codes[index + 2] integerValue]; - if (SPLCodesAreXTermSequence(code1, code2)) { + if (SPLCodesAreTrueColorSequence(code1, code2)) { + // 38;2;R;G;B or 48;2;R;G;B + if (index + 4 < [codes count]) { + NSInteger r = code3; + NSInteger g = [codes[index + 3] integerValue]; + NSInteger b = [codes[index + 4] integerValue]; + UIColor *trueColor = [UIColor colorWithRed:r / 255.0 + green:g / 255.0 + blue:b / 255.0 + alpha:1.0]; + BOOL isForeground = (code1 == SPLSGRCodeXTermForeground); + if (options.isReverse) { + isForeground = !isForeground; + } + if (isForeground) { + self.lastColor = trueColor; + } else { + self.lastBGColor = trueColor; + } + didColor = YES; + index += 5; + } else { + break; + } + } else if (SPLCodesAreXTermSequence(code1, code2)) { [self parseXtermColorForColor:code3 isForeground:(code1 == SPLSGRCodeXTermForeground) options:options]; didColor = YES; + index += 3; + } else { + index++; } } } diff --git a/src/Mudrammer/Network/SSMUDSocket.h b/src/Mudrammer/Network/SSMUDSocket.h index d11ce33..32d8807 100644 --- a/src/Mudrammer/Network/SSMUDSocket.h +++ b/src/Mudrammer/Network/SSMUDSocket.h @@ -1,81 +1,18 @@ -// -// SSMUDSocket.h -// Mudrammer -// -// Created by Jonathan Hersh on 1/15/13. -// Copyright (c) 2013 Jonathan Hersh. All rights reserved. -// - -#import +@import Foundation; #import "SSAttributedLineGroup.h" -@protocol SSMUDSocketDelegate; - -/** - * SSMUDSocket encapsulates the network connection for a MUD's session. - * User-entered commands are passed to the socket for transmission over the network. - * Text received from the server is parsed into a group of attributed strings - * for display to the user. - */ -@interface SSMUDSocket : NSObject - -@property (nonatomic, weak) id delegate; - -- (instancetype) init NS_UNAVAILABLE; - -- (instancetype) initWithSocket:(GCDAsyncSocket *)socket NS_DESIGNATED_INITIALIZER; - -#pragma mark - Connection Lifecycle - -// Attempt a connection to the specified hostname and port with a standard timeout. -- (BOOL)connectToHostname:(NSString *)hostname - onPort:(NSUInteger)port - error:(NSError **)error; - -// YES if the socket is neither connected nor in the process of connecting. -- (BOOL)isDisconnected; - -// Disconnect immediately. -- (void)disconnect; - -// Detaches internal socket delegates. -- (void)resetSocket; - -// Send some text (user entered commands/aliases) -- (void)sendUserCommands:(NSArray *)commands; +@class SSMUDSocket; -// NAWS -- (void) sendNAWSWithSize:(CGSize)size; - -// Should we echo entered text to the user? -@property (nonatomic, readonly) BOOL shouldEchoText; - -// Set before connecting to enable TLS on the socket. -@property (nonatomic, assign) BOOL isSecure; - -@end - -// Below protocol methods are called on arbitrary queues @protocol SSMUDSocketDelegate @required - -// ask the delegate if this connection should attempt to secure itself with SSL - (BOOL) mudsocketShouldAttemptSSL:(SSMUDSocket *)socket; - -// We received text, which was parsed into an attributed line - (void) mudsocket:(SSMUDSocket *)socket didReceiveAttributedLineGroup:(SSAttributedLineGroup *)group; @optional - -// connecting - (void) mudsocketDidConnectToHost:(SSMUDSocket *)socket; - (void) mudsocket:(SSMUDSocket *)socket didDisconnectWithError:(NSError *)err; - -// We received some MSSP data for the current world - (void) mudsocket:(SSMUDSocket *)socket receivedMSSPData:(NSDictionary *)MSSPData; - -// We received a GMCP message - (void) mudsocket:(SSMUDSocket *)socket receivedGMCPModule:(NSString *)module data:(NSDictionary *)data; @end diff --git a/src/Mudrammer/Network/SSMUDSocket.m b/src/Mudrammer/Network/SSMUDSocket.m deleted file mode 100644 index fa72638..0000000 --- a/src/Mudrammer/Network/SSMUDSocket.m +++ /dev/null @@ -1,321 +0,0 @@ -// -// SSMUDSocket.m -// Mudrammer -// -// Created by Jonathan Hersh on 1/15/13. -// Copyright (c) 2013 Jonathan Hersh. All rights reserved. -// - -#import "SSMUDSocket.h" -#import "SSAdvSettingsController.h" -#import "SSANSIEngine.h" -#import "SPLTelnetLib.h" -#import "NSData+SPLDataParsing.h" -#import "NSAttributedString+SPLAdditions.h" -#import "NSCharacterSet+SPLAdditions.h" -#import "SSStringCoder.h" - -#define SPLSOCKET_BRIDGE_STRING __bridge NSString * -#define SPLSOCKET_BRIDGE_NUMBER __bridge NSNumber * - -@interface GCDAsyncSocket (SPLAdditions) - -- (void) readFromSocket; - -@end - -@interface SSMUDSocket () - -- (void) informDelegateWithSelector:(SEL)selector object:(id)object; - -// Perform append from cache and split on broken ANSI sequences -- (NSString *) stringBySplittingAndCachingString:(NSString *)string; - -@property (nonatomic, strong) GCDAsyncSocket *socket; -@property (nonatomic, strong) NSMutableString *dataCache; -@property (nonatomic, strong) SSANSIEngine *ansiEngine; -@property (nonatomic, strong) SPLTelnetLib *telnetLib; - -// Ensure that text is processed one at a time and FIFO -@property (nonatomic, strong) NSOperationQueue *parsingQueue; - -@end - -@implementation GCDAsyncSocket (SPLAdditions) - -- (void)readFromSocket { - [self readDataWithTimeout:-1 tag:0]; -} - -@end - -@implementation SSMUDSocket - -#pragma mark - init - -- (instancetype)initWithSocket:(GCDAsyncSocket *)socket { - - if ((self = [super init])) { - _dataCache = [NSMutableString string]; - _parsingQueue = [NSOperationQueue ss_serialOperationQueue]; - _ansiEngine = [SSANSIEngine new]; - - _socket = socket; - self.socket.delegate = self; - self.socket.delegateQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); - } - - return self; -} - -- (void)dealloc { - [self.parsingQueue cancelAllOperations]; - self.socket.delegate = nil; - _delegate = nil; - [self.socket disconnect]; -} - -#pragma mark - Connection Lifecycle - -- (void)resetSocket { - [self.socket setDelegate:nil delegateQueue:NULL]; -} - -- (BOOL)connectToHostname:(NSString *)hostname - onPort:(NSUInteger)port - error:(NSError *__autoreleasing *)error { - - return [self.socket connectToHost:hostname - onPort:(uint16_t)port - withTimeout:30 - error:error]; -} - -- (BOOL)isDisconnected { - return [self.socket isDisconnected]; -} - -- (void)disconnect { - [self.socket disconnect]; -} - -#pragma mark - informing delegate - -- (void)informDelegateWithSelector:(SEL)selector object:(id)object { - id del = self.delegate; - if ([del respondsToSelector:selector]) { - dispatch_async( dispatch_get_global_queue( QOS_CLASS_DEFAULT, 0), ^{ - NSString *sel = NSStringFromSelector(selector); - - if( [sel isEqualToString:NSStringFromSelector(@selector(mudsocketDidConnectToHost:))] ) - [del mudsocketDidConnectToHost:self]; - else if( [sel isEqualToString:NSStringFromSelector(@selector(mudsocket:didDisconnectWithError:))] ) - [del mudsocket:self didDisconnectWithError:object]; - }); - } -} - -#pragma mark - socket options states - -- (BOOL)shouldEchoText { - return self.telnetLib.shouldEchoText; -} - -- (NSString *)stringBySplittingAndCachingString:(NSString *)string { - - NSMutableString *fullStr = [NSMutableString new]; - - // Append from cache - if ([self.dataCache length] > 0) { - DLog(@"append from %@", self.dataCache); - - [fullStr appendString:self.dataCache]; - - [self.dataCache deleteCharactersInRange:NSMakeRange(0, [self.dataCache length])]; - } - - [fullStr appendString:string]; - - if ([fullStr length] == 0) { - return @""; - } - - // Find the last CSI in this sequence, if any - NSString *searchStr = [kANSIEscapeCSI substringToIndex:1]; - - NSRange CSIRange = [fullStr rangeOfString:searchStr - options:NSBackwardsSearch | NSLiteralSearch]; - - if (CSIRange.location == NSNotFound) { - return fullStr; - } - - // We've found the start of an ANSI CSI sequence. Was it terminated properly? - NSRange CSITerminationRange = [fullStr rangeOfCharacterFromSet:[NSCharacterSet CSITerminationCharacterSet] - options:NSLiteralSearch - range:NSMakeRange(CSIRange.location, [fullStr length] - CSIRange.location)]; - - if (CSITerminationRange.location == NSNotFound) { - // We have an ANSI CSI that was started but not terminated. - // Split the string here and cache it for next time. - - NSRange cacheRange = NSMakeRange(CSIRange.location, [fullStr length] - CSIRange.location); - - [self.dataCache appendString:[fullStr substringWithRange:cacheRange]]; - - [fullStr deleteCharactersInRange:cacheRange]; - - DLog(@"caching %@", self.dataCache); - } - - return fullStr; -} - -#pragma mark - Read/write - -- (void)sendUserCommands:(NSArray *)commands { - if (![self.socket isConnected]) { - return; - } - - self.ansiEngine.defaultTextColor = [[SSThemes sharedThemer] valueForThemeKey:kThemeFontColor]; - - [self.telnetLib sendUserCommands:commands]; -} - -#pragma mark - NAWS - -- (void)sendNAWSWithSize:(CGSize)size { - [self.telnetLib sendNAWSWithSize:size]; -} - -#pragma mark - GCDAsyncSocketDelegate - -- (void)socketDidSecure:(GCDAsyncSocket *)sock { - SSAttributedLineGroup *secureLine = [SSAttributedLineGroup lineGroupWithAttributedString: - [NSAttributedString worldStringForString:NSLocalizedString(@"SSL_SUCCESS", nil)]]; - - id del = self.delegate; - dispatch_async(dispatch_get_main_queue(), ^{ - [del mudsocket:self didReceiveAttributedLineGroup:secureLine]; - }); -} - -- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { - // reset telnet lib - _telnetLib = [[SPLTelnetLib alloc] initWithStringCoder:[SSStringCoder new]]; - self.telnetLib.delegate = self; - - // Reset default string color - self.ansiEngine.defaultTextColor = [[SSThemes sharedThemer] valueForThemeKey:kThemeFontColor]; - - // Clear saved options - self.dataCache = [NSMutableString new]; - - // Start TLS before any reads if the connection should be secure - if (self.isSecure) { - [sock startTLS:@{ - (SPLSOCKET_BRIDGE_STRING)kCFStreamSSLValidatesCertificateChain : (SPLSOCKET_BRIDGE_NUMBER)kCFBooleanFalse, - GCDAsyncSocketSSLProtocolVersionMin : @(tls_protocol_version_TLSv12), - }]; - } - - [self.telnetLib socketDidConnect]; - - // inform delegate - [self informDelegateWithSelector:@selector(mudsocketDidConnectToHost:) - object:nil]; - - // Start reading! - [sock readFromSocket]; -} - -- (void)socketDidDisconnect:(SSMUDSocket *)sock withError:(NSError *)err { - @weakify(self); - [self.parsingQueue ss_addBlockOperationWithBlock:^(SSBlockOperation *operation) { - @strongify(self); - self.telnetLib = nil; - [self informDelegateWithSelector:@selector(mudsocket:didDisconnectWithError:) - object:err]; - }]; -} - -- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { - @weakify(self); - - [self.parsingQueue ss_addBlockOperationWithBlock:^(SSBlockOperation *operation) { - @strongify(self); - if ([operation isCancelled]) { - return; - } - - // Initiate telnet library processing - DLog(@"RCV %@ bytes", @([data length])); - [self.telnetLib receivedSocketData:data]; - }]; - - // Continue reading - [sock readFromSocket]; -} - -- (void)socket:(SSMUDSocket *)sock didWriteDataWithTag:(long)tag { - // anything to do here? -} - -#pragma mark - SPLTelnetLibDelegate - -- (void)telnetLibrary:(SPLTelnetLib *)library receivedMSSPData:(NSDictionary *)MSSPData { - id del = self.delegate; - if ([del respondsToSelector:@selector(mudsocket:receivedMSSPData:)]) { - [del mudsocket:self receivedMSSPData:MSSPData]; - } -} - -- (void)telnetLibrary:(SPLTelnetLib *)library receivedGMCPModule:(NSString *)module data:(NSDictionary *)data { - id del = self.delegate; - if ([del respondsToSelector:@selector(mudsocket:receivedGMCPModule:data:)]) { - [del mudsocket:self receivedGMCPModule:module data:data]; - } -} - -- (void)telnetLibrary:(SPLTelnetLib *)library encounteredFatalError:(NSString *)error { - DLog(@"Fatal telnet error: %@", error); - // TODO: surface this error to the user - [self.socket disconnect]; -} - -- (void)telnetLibrary:(SPLTelnetLib *)library mustSendData:(NSData *)data { - DLog(@"Sending data %@", data.charCodeString); - [self.socket writeData:data withTimeout:-1 tag:0]; -} - -- (void)telnetLibrary:(SPLTelnetLib *)library shouldPrintString:(NSString *)string { - [self.parsingQueue ss_addBlockOperationWithBlock:^(SSBlockOperation *operation) { - if ([string length] == 0 || [operation isCancelled]) { - return; - } - - // Split and cache - NSString *fullStr = [self stringBySplittingAndCachingString:string]; - - if ([fullStr length] == 0 || [operation isCancelled]) { - return; - } - - // Parse ANSI into an attributed line group - SSAttributedLineGroup *group = [self.ansiEngine parseANSIString:fullStr]; - - if ([operation isCancelled]) { - return; - } - - id del = self.delegate; - if ([del respondsToSelector:@selector(mudsocket:didReceiveAttributedLineGroup:)]) { - dispatch_async( dispatch_get_main_queue(), ^{ - [del mudsocket:self didReceiveAttributedLineGroup:group]; - }); - } - }]; -} - -@end diff --git a/src/Mudrammer/Network/SSMUDSocket.swift b/src/Mudrammer/Network/SSMUDSocket.swift new file mode 100644 index 0000000..653da01 --- /dev/null +++ b/src/Mudrammer/Network/SSMUDSocket.swift @@ -0,0 +1,267 @@ +import UIKit +import CocoaAsyncSocket +import MTHClient + +@objc(SSMUDSocket) @objcMembers +final class SSMUDSocket: NSObject, GCDAsyncSocketDelegate { + + weak var delegate: SSMUDSocketDelegate? + + private let socket: GCDAsyncSocket + private var telnetSession: TelnetClientSession? + private let stringCoder = SSStringCoder() + private let ansiEngine = SSANSIEngine() + private var dataCache = "" + private let parsingQueue = OperationQueue.ss_serial()! + + @objc var isSecure = false + + @objc var shouldEchoText: Bool { + guard let session = telnetSession else { return true } + return !session.serverEcho + } + + // MARK: - Init + + @objc init(socket: GCDAsyncSocket) { + self.socket = socket + super.init() + self.socket.setDelegate(self, delegateQueue: DispatchQueue.global(qos: .default)) + } + + deinit { + parsingQueue.cancelAllOperations() + socket.setDelegate(nil, delegateQueue: nil) + delegate = nil + socket.disconnect() + } + + // MARK: - Connection Lifecycle + + @objc func connect(toHostname hostname: String, onPort port: UInt, error: NSErrorPointer) -> Bool { + do { + try socket.connect(toHost: hostname, onPort: UInt16(port), withTimeout: 30) + return true + } catch let err as NSError { + error?.pointee = err + return false + } catch { + return false + } + } + + @objc func isDisconnected() -> Bool { + socket.isDisconnected() + } + + @objc func disconnect() { + socket.disconnect() + } + + @objc func resetSocket() { + socket.setDelegate(nil, delegateQueue: nil) + } + + // MARK: - Send + + @objc func sendUserCommands(_ commands: [Any]) { + guard socket.isConnected() else { return } + ansiEngine.defaultTextColor = SSThemes.sharedThemer().value(forThemeKey: kThemeFontColor) as? UIColor + let data = stringCoder.data(forUserCommands: commands)! + socket.write(data, withTimeout: -1, tag: 0) + } + + @objc(sendNAWSWithSize:) func sendNAWS(with size: CGSize) { + telnetSession?.sendWindowSize(width: Int(size.width), height: Int(size.height)) + } + + // MARK: - GCDAsyncSocketDelegate + + func socketDidSecure(_ sock: GCDAsyncSocket) { + let secureLine = SSAttributedLineGroup( + attributedString: NSAttributedString.worldString(for: NSLocalizedString("SSL_SUCCESS", comment: "")) + )! + let del = delegate + DispatchQueue.main.async { + del?.mudsocket(self, didReceive: secureLine) + } + } + + func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) { + telnetSession = TelnetClientSession( + delegate: self, + terminalType: "Wammer", + windowWidth: 80, + windowHeight: 24, + mttsFlags: 271 + ) + + ansiEngine.defaultTextColor = SSThemes.sharedThemer().value(forThemeKey: kThemeFontColor) as? UIColor + dataCache = "" + + if isSecure { + sock.startTLS([ + kCFStreamSSLValidatesCertificateChain as String: false as NSNumber, + GCDAsyncSocketSSLProtocolVersionMin: NSNumber(value: tls_protocol_version_t.TLSv12.rawValue), + ]) + } + + let del = delegate + if del?.responds(to: #selector(SSMUDSocketDelegate.mudsocketDidConnect(toHost:))) == true { + DispatchQueue.global(qos: .default).async { + del?.mudsocketDidConnect?(toHost: self) + } + } + + sock.readData(withTimeout: -1, tag: 0) + } + + func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: (any Error)?) { + parsingQueue.ss_addBlockOperation { [weak self] _ in + guard let self else { return } + self.telnetSession = nil + let del = self.delegate + if del?.responds(to: #selector(SSMUDSocketDelegate.mudsocket(_:didDisconnectWithError:))) == true { + DispatchQueue.global(qos: .default).async { + del?.mudsocket?(self, didDisconnectWithError: err as NSError?) + } + } + } + } + + func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) { + parsingQueue.ss_addBlockOperation { [weak self] operation in + guard let self, !(operation?.isCancelled ?? true) else { return } + + let bytes = [UInt8](data) + guard let session = self.telnetSession else { return } + + let cleanBytes = session.processInput(bytes) + + if !cleanBytes.isEmpty { + let cleanData = Data(cleanBytes) + let string = self.stringCoder.stringByDecodingData(withCurrentEncoding: cleanData) ?? "" + self.processReceivedString(string, operation: operation) + } + } + + sock.readData(withTimeout: -1, tag: 0) + } + + func socket(_ sock: GCDAsyncSocket, didWriteDataWithTag tag: Int) { + } + + // MARK: - String Processing + + private func processReceivedString(_ string: String, operation: SSBlockOperation?) { + guard !string.isEmpty, !(operation?.isCancelled ?? true) else { return } + + let fullStr = stringBySplittingAndCachingString(string) + + guard !fullStr.isEmpty, !(operation?.isCancelled ?? true) else { return } + + let group = ansiEngine.parseANSIString(fullStr)! + + guard !(operation?.isCancelled ?? true) else { return } + + let del = delegate + DispatchQueue.main.async { + del?.mudsocket(self, didReceive: group) + } + } + + private func stringBySplittingAndCachingString(_ string: String) -> String { + var fullStr = "" + + if !dataCache.isEmpty { + fullStr.append(dataCache) + dataCache = "" + } + + fullStr.append(string) + + guard !fullStr.isEmpty else { return "" } + + let searchStr = String(kANSIEscapeCSI.prefix(1)) + guard let csiRange = fullStr.range(of: searchStr, options: .backwards) else { + return fullStr + } + + let searchRange = csiRange.lowerBound.. + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + This class is in the public domain. +Originally created by Robbie Hanson in Q3 2010. +Updated and maintained by Deusty LLC and the Apple development community. + +https://github.com/robbiehanson/CocoaAsyncSocket + Title + CocoaAsyncSocket + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2012 Daniel Amitay (http://danielamitay.com) + +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. + Title + DAKeyboardControl + Type + PSGroupSpecifier + + + FooterText + FXForms + +Copyright (C) 2014 Charcoal Design + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + Title + FXForms + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2015 Jesse Squires + +http://www.hexedbits.com + +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. + Title + JSQSystemSoundPlayer + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2014 Jared Sinclair + +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. + Title + JTSImageViewController + Type + PSGroupSpecifier + + + FooterText + BSD License + +For KVOController software + +Copyright (c) 2014, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Title + KVOController + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2012 - 2014 Justin Spahr-Summers + +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. + Title + libextobjc + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry + +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. + Title + Masonry + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2014 Jonathon Mah + +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. + Title + MyLilTimer + Type + PSGroupSpecifier + + + FooterText + OSCache + +Copyright (C) 2014 Charcoal Design + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + Title + OSCache + Type + PSGroupSpecifier + + + FooterText + Copyright 2011 ESCOZ Inc - http://escoz.com + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Title + QuickDialog + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2012-2013 Sam Soffes, http://soff.es + +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. + Title + SAMRateLimit + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2013,2014 Jonathan Hersh + +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. + Title + SSAccessibility + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2013 Jonathan Hersh + +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. + Title + SPLCore + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2015 Jonathan Hersh + +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. + Title + SPLUserActivity + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2013-2014 Jonathan Hersh +A Splinesoft production. + +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. + Title + SSDataSources + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2013 Jonathan Hersh + +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. + Title + SSOperations + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2011 Mattt Thompson (http://mattt.me/) + +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. + Title + TTTAttributedLabel + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2013-2015 Vincent Tourraine (http://www.vtourraine.net) + +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. + Title + VTAcknowledgementsViewController + Type + PSGroupSpecifier + + + FooterText + + Title + Acknowledgements + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/src/Mudrammer/Supporting Files/Wammer-Bridging-Header.h b/src/Mudrammer/Supporting Files/Wammer-Bridging-Header.h index e725da0..f963847 100644 --- a/src/Mudrammer/Supporting Files/Wammer-Bridging-Header.h +++ b/src/Mudrammer/Supporting Files/Wammer-Bridging-Header.h @@ -4,10 +4,17 @@ // @import UIKit; +@import SSOperations; #import "SSMRConstants.h" #import "SPLNotificationManager.h" #import "SSThemes.h" +#import "SSMUDSocket.h" +#import "SSANSIEngine.h" +#import "SSAttributedLineGroup.h" +#import "SSStringCoder.h" +#import "NSCharacterSet+SPLAdditions.h" +#import "NSAttributedString+SPLAdditions.h" #import "SSWorldDisplayController.h" #import "SPLHandoffWebViewController.h"