(* $Id: telnet_client.mli 1612 2011-06-07 23:41:05Z gerd $ * ---------------------------------------------------------------------- * *) (** Telnet client * * This is a Telnet client providing the basic Telnet services. It * supports sending and receiving data (asynchronously), and the * negotiation of Telnet options, but it does not implement any option. *) exception Telnet_protocol of exn;; (** Wrapper for exceptions that already passed the exception handler. *) type telnet_command = Telnet_data of string (** User data *) | Telnet_nop (** No operation *) | Telnet_dm (** data mark *) | Telnet_brk (** break *) | Telnet_ip (** interrupt process *) | Telnet_ao (** abort output *) | Telnet_ayt (** are you there? *) | Telnet_ec (** erase character *) | Telnet_el (** erase line *) | Telnet_ga (** Go ahead *) | Telnet_sb of char (** Begin of subnegotiation *) | Telnet_se (** End of subnegotation *) | Telnet_will of char (** Acknowledges that option is in effect *) | Telnet_wont of char (** Acknowledges that option is rejected *) | Telnet_do of char (** Requests to turn on an option *) | Telnet_dont of char (** Requests to turn off an option *) | Telnet_unknown of char (** Unknown command *) | Telnet_eof (** End of file *) | Telnet_timeout (** Timeout event *) (** A [telnet_command] is the interpretation of the octets in a Telnet * session, i.e. it is one level above the octet stream. See RFC 854 * for an explanation what the commands mean. [Telnet_data] represents * the data chunks between the commands. Note that you do not need * to double octets having value 255; this is done automatically. * [Telnet_unknown] represents any command not covered by RFC 854, for * example the End-of-record-mark (introduced in RFC 885) would be * [Telnet_unknown '\239']. [Telnet_eof] represents the end of the octet * stream, useable in both directions. [Telnet_timeout] is added to the * input queue if I/O has not been happened for the configured period * of time. *) type telnet_options = { connection_timeout : float; verbose_input : bool; verbose_output : bool; } (** [telnet_options]: modifies the behaviour of the client. Do not mix these * options up with the options negotiated with the remote side. * * - [connection_timeout]: After this period of time (in seconds) a * [Telnet_timeout] pseudo-command is added to * the input queue, and the connection is * aborted. * - [verbose_input]: Enables printing of input events to {!Netlog.Debug}. * - [verbose_output]: Enables printing of output events to {!Netlog.Debug} *) type telnet_negotiated_option = Telnet_binary (** see RFC 856 *) | Telnet_echo (** see RFC 857 *) | Telnet_suppress_GA (** see RFC 858 *) | Telnet_status (** see RFC 859 *) | Telnet_timing_mark (** see RFC 860 *) | Telnet_ext_opt_list (** see RFC 861 *) | Telnet_end_of_rec (** see RFC 885 *) | Telnet_window_size (** see RFC 1073 *) | Telnet_term_speed (** see RFC 1079 *) | Telnet_term_type (** see RFC 1091 *) | Telnet_X_display (** see RFC 1096 *) | Telnet_linemode (** see RFC 1184 *) | Telnet_flow_ctrl (** see RFC 1372 *) | Telnet_auth (** see RFC 1416 *) | Telnet_new_environ (** see RFC 1572 and 1571 *) | Telnet_option of int (** all other options *) (** [telnet_negotiated_option]: names for the most common options, and * the generic name [Telnet_option] for other options. *) type telnet_option_state = Not_negotiated | Accepted | Rejected (** *) (** An option has one of three states: * - [Not_negotiated]: There was no negotiation about the option. This means * that the option is turned off (but this client is allowed to reject * it explicitly) * - [Accepted]: Both sides have accepted the option. * - [Rejected]: One side has rejected the option. This also means that the * option is off, but the client refuses to send further acknoledgements * that the option is off (to avoid endless negotiation loops). *) val char_of_option : telnet_negotiated_option -> char (** Converts the option name to the character representing it on the * octet-stream level. *) val option_of_char : char -> telnet_negotiated_option (** Converts a character representing an option to the internal option * name. *) type telnet_connector = Telnet_connect of (string * int) | Telnet_socket of Unix.file_descr (** *) (** Connectors: * - [Telnet_connect(host,port)]: The client connects to this port. * - [Telnet_socket s]: The client uses an already connected socket. * * Why [Telnet_socket]? Telnet is a symmetrical protocol; client and servers * implement the same protocol features (the only difference is the * environment: a client is typically connected with a real terminal; a server * is connected with a pseudo terminal). This simply means that this * implementation of a client can also be used as a server implementation. * You need only to add code which accepts new connections and which passes * these connections over to a [telnet_session] object via [Telnet_socket]. *) (** A telnet session *) class telnet_session : object (** Overwiew * * The [telnet_session] object has two queues, one for arriving data, * one for data to send. * Once the session object is attached to an event system, it connects * to the remote peer, and processes the queues. Input is appended to * the input queue; output found on the output queue is sent to the * other side. * If input arrives, a callback function is invoked. * You may close the output side of the socket by putting [Telnet_eof] * onto the output queue. * Once the EOF marker has been received, a [Telnet_eof] is appended to * the input queue, and the connection is closed (completely). The * session object detaches from the event system automatically in this * case. * * {b Hints} * * Set an input handler as callback function in the session object. * The input handler is called when new input data have been arrived. * It should inspect the input queue, process the queue as much as * possible, and it should remove the processed items from the queue. * While processing, it may add new items to the output queue. * * If you are not within the callback function and add items to the * output queue, the session object will not detect that there are * new items to send - unless you invoke the [update] method. * * If you want option negotiation, it is the simplest way to use * the special option negotiation methods. Configure the options * as you want (invoking [enable], [disable] etc), but do not forget * to modify the way input is processed. Every [Telnet_will], [_wont], * [_do], and [_dont] command must be passed to [process_option_command]. *) method set_connection : telnet_connector -> unit (** Sets the host name and the port of the remote server to contact. *) method set_event_system : Unixqueue.event_system -> unit (** Sets the event system to use. By default, a private event system * is used. *) method set_callback : (bool -> unit) -> unit (** Sets the callback function. This function is called after new * commands have been put onto the input queue. * The argument passed to the callback function indicates whether * a 'Synch' sequence was received from the remote side or not. * * {b Note Synch:} If the client sees a data mark command it will assume * that it is actually a Synch sequence. The client automatically * discards any [Telnet_data] commands from the input queue (but not * [Telnet_data]s inside subnegotiations). The data mark command * itself remains on the queue. *) method set_exception_handler : (exn -> unit) -> unit (** Sets the exception handler. Every known error condition is * caught and passed to the exception handler. * The exception handler can do whatever it wants to do. If it * raises again an exception, the new exception is always propagated * up to the caller (whoever this is). Often the caller is the * event system scheduler (i.e. [Unixqueue.run]); see the documention * there. * * If you do not set the exception handler, a default handler is * active. It first resets the session (see method [reset]), and * then wraps the exception into the [Telnet_protocol] exception, * and raises this exception again. *) method output_queue : telnet_command Queue.t (** The queue of commands to send to the remote side. If you add new * commands to this queue, do not forget to invoke the [update] * method which indicates to the event system that new data to * send is available. * After commands have been sent, they are removed from the queue. *) method input_queue : telnet_command Queue.t (** The queue of commands received from the remote side. This class * only adds commands to the queue (and invokes the callback * function). The user of this class is responsible for removing * commands from the queue which have been processed. *) method get_options : telnet_options (** Get the configuration options. *) method set_options : telnet_options -> unit (** Set the configuration options. *) method reset : unit -> unit (** Closes the connection immediately and empties all queues. * All negotiated options are reset, too. *) (** Telnet options * * The following methods deal with Telnet protocol options. These * are negotiated between local and remote side by the Will, Won't, * Do and Don't commands. * * The "local" options describe the modification of the behaviour * of the local side; the "remote" options describe the modifications * of the remote side. Both set of options are independent. * This object may track the set of accepted and rejected options * if the following methods are used; but this works only if * the [Telnet_will], [_wont], [_do], and [_dont] commands received from * the remote side are processed by [process_option_command]. So * you need to invoke this method for the mentioned commands in * your command interpretation loop. * * The idea is: If you {b enable} an option, it is possible to * switch it on. If the remote side requests the option to be enabled, * the request will be acknowledged. If the remote side does not * request the option, it remains off. * * You can also actively demand an option ([offer_local_option], * [request_remote_option]); this is of course only possible if * the option is already enabled. In this case the client tries * actively to switch it on. * * You can also {b disable} an option. If the option is on, the * client actively rejects the option; following the Telnet protocol * this is always possible (rejections cannot be rejected). * * The [reset] methods are somewhat dangerous. They simply reset * the internal state of the client, but do not negotiate. This * possibility was added to allow the Timing Mark option to send * again timing marks even if the previous timing marks have * already been accepted. After [reset], the client thinks the * option was never negotiated; but nothing is done to tell * the remote side about this. * * [option_negotiation_is_over]: true if no option negotiation is * pending (i.e. nothing has still to be acknowledged). *) method enable_local_option : telnet_negotiated_option -> unit method enable_remote_option : telnet_negotiated_option -> unit method disable_local_option : telnet_negotiated_option -> unit method disable_remote_option : telnet_negotiated_option -> unit method offer_local_option : telnet_negotiated_option -> unit method request_remote_option : telnet_negotiated_option -> unit method reset_local_option : telnet_negotiated_option -> unit method reset_remote_option : telnet_negotiated_option -> unit method get_local_option : telnet_negotiated_option -> telnet_option_state method get_remote_option : telnet_negotiated_option -> telnet_option_state method option_negotiation_is_over : bool method process_option_command : telnet_command -> unit method fetch_subnegotiation : string option (** This method should be called as follows: * If you find a [Telnet_sb] at the beginning of the input queue, * remove this command [Queue.take], and invoke [fetch_subnegotiation]. * This method scans the queue and looks for the associated * [Telnet_se] command. If it does not find it, [None] is returned. * If [Telnet_se] is found, the parameter enclosed by the two commands * is returned as [Some s] where [s] is the parameter string. Furthermore, * in the latter case the data items and the closing [Telnet_se] are * removed from the queue. *) (** Running the session *) method attach : unit -> unit (** Attach to the event system. After being attached, the client * is ready to work. *) method run : unit -> unit (** Run the event system *) method update : unit -> unit (** If there are commands in the output queue, the event system is * signaled that this client wants to do network I/O. *) method send_synch : telnet_command list -> unit (** At the next output oppurtunity, a Synch sequence is sent to * the remote peer. This means that the passed commands, extended * by an additional Data Mark command, are sent to the peer as * urgent data. * * Sending a Synch sequence has higher priority than the output * queue; processing of the output queue is deferred until the * Synch sequence has been completely sent. *) method expect_input : bool -> unit (** Set whether the timeout value is to be applied to the input side of the connection. This is [true] by default. *) end ;; (** {1 Debugging} *) module Debug : sig val enable : bool ref (** Enables {!Netlog}-style debugging of this module By default, the exchanged Telnet commands are logged. This can be extended by setting the [verbose_input] and [verbose_output] options. *) end