From: Gerd Stolpmann <info@gerd-stolpmann.de>
To: Andrei Errapart <andreie@no.spam.ee>
Cc: caml-list@inria.fr
Subject: Re: [Caml-list] calling telnet from a caml program
Date: Wed, 6 Nov 2002 21:56:48 +0100 [thread overview]
Message-ID: <20021106205648.GA1038@ice.gerd-stolpmann.de> (raw)
In-Reply-To: <Pine.LNX.4.44.0211061910030.1661-100000@no.spam.ee>; from andreie@no.spam.ee on Mit, Nov 06, 2002 at 18:20:05 +0100
[-- Attachment #1: Type: text/plain, Size: 970 bytes --]
Am 2002.11.06 18:20 schrieb(en) Andrei Errapart:
> On Wed, 6 Nov 2002, Alan Schmitt wrote:
> ...
> > That's what I was afraid of ... So on to the next question: is it
> > possible to use pseudy-ttys with caml ? Or should I start thinking about
> > reimplementing the telnet client protocol ...
>
> If it is possible in C, then it is possible in OCaml, too. Most
> probably some termios(3) calls are needed, but not much more. By the
> way, Gerd Stolpmann mentions telnet client in his Netclient library.
Yes, there is a telnet client in netclient version 0.90.1. I have attached
the MLI interface, and a simple example.
You can download netclient at
http://www.gerd-stolpmann.de/packages/netclient-0.90.1.tar.gz
Gerd
------------------------------------------------------------
Gerd Stolpmann * Viktoriastr. 45 * 64293 Darmstadt * Germany
gerd@gerd-stolpmann.de http://www.gerd-stolpmann.de
------------------------------------------------------------
[-- Attachment #2: telnet_client.mli --]
[-- Type: text/plain, Size: 14226 bytes --]
(* $Id: telnet_client.mli,v 1.2 2001/09/05 21:41:48 gerd Exp $
* ----------------------------------------------------------------------
*
*)
(* 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
| Telnet_nop
| 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 RFC854
* 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 RFC854, for
* example the End-of-record-mark (introduced in RFC885) 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_connection : bool;
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_connection': Enables printing of connection events to stderr.
* 'verbose_input': Enables printing of input events to stderr.
* 'verbose_output': Enables printing of output events to stderr.
*)
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
;;
(* telnet_connector:
* 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.
*)
class telnet_session :
object
(* GENERAL:
*
* 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.
*
* HOW TO USE THE SESSION OBJECT:
*
* Pass an input handler as callback function to 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.
*
* 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_datas 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.
*)
(* 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 ENABLE an option, it is possible to
* switch it on. If the remote side requests an enabled option,
* the request will be acknowledged. If the remote side does not
* request an enabled 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 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.
*)
(* --- *)
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.
*)
end
;;
(* ======================================================================
* History:
*
* $Log: telnet_client.mli,v $
* Revision 1.2 2001/09/05 21:41:48 gerd
* Fixed types.
*
* Revision 1.1 2000/02/18 01:42:32 gerd
* Initial revision.
*
*
*)
[-- Attachment #3: telnet.ml --]
[-- Type: text/plain, Size: 4603 bytes --]
#require "netclient";;
(* This is an example for the telnet client. The function below
* connects with localhost, and logs the user in. It simulates
* keyboard typing for the username and the password, and finally
* starts the command "ls".
*
* The example may or may not work with your version of telnetd.
* The program expects the string "login" before the user name must
* be typed in, and it expects the string "password" before the password
* must be entered. Furthermore, a new command line is recognized
* by the characters >, # or $.
*)
open Telnet_client;;
type state =
Start (* just connected *)
| Username_sent (* the user name has been sent to the server *)
| Password_sent (* the password has been sent to the server *)
| Command_sent (* the command to execute has been sent to the server *)
;;
let login_re = Str.regexp_case_fold "\\(.\\|\n\\)*login";;
let passwd_re = Str.regexp_case_fold "\\(.\\|\n\\)*password";;
let cmd_re = Str.regexp "\\(.\\|\n\\)*[>$#]";;
let login_and_ls username password =
(* Create a new event system, and the telnet session. We need the
* event system only to call Unixqueue.once.
*)
let esys = Unixqueue.create_unix_event_system() in
let session = new telnet_session in
let state = ref Start in
let send_string s new_state =
(* Emulate keyboard typing of the string s. Between the characters there
* is a delay of 0.1 seconds.
* When the string has been completely sent, change the state to
* new_state.
*)
let t = ref 0.1 in
let g = Unixqueue.new_group esys in
let l = String.length s in
for i = 0 to l - 1 do
let c = s.[i] in
let cs = if c = '\n' then "\r\n" else String.make 1 c in
(* Do the function !t seconds in the future: *)
Unixqueue.once esys g !t
(fun () ->
Queue.add (Telnet_data cs) session#output_queue;
(* We must call update because we are outside of the regular
* callback function. Otherwise the session object would not
* notice that the queue has been extended.
*)
session # update();
if i = l-1 then
state := new_state
);
t := !t +. 0.1;
done
in
let got_input is_urgent =
(* This is the callback function. The session object calls it when
* telnet commands have been added to the input queue.
*)
let oq = session # output_queue in
let iq = session # input_queue in
(* Process the input queue command by command: *)
while Queue.length iq > 0 do
let cmd = Queue.take iq in
match cmd with
| Telnet_will _
| Telnet_wont _
| Telnet_do _
| Telnet_dont _ ->
(* These are the commands used to negotiate the telnet options.
* The session object can do it for you.
*)
session # process_option_command cmd
| Telnet_data s ->
(* The data string s has been received. *)
( match !state with
Start ->
if Str.string_match login_re s 0 then begin
(* Assume the host wants our username, and send it. *)
send_string (username ^ "\n") Username_sent
end
| Username_sent ->
if Str.string_match passwd_re s 0 then begin
(* Assume the host wants our password, and send it. *)
send_string (password ^ "\n") Password_sent;
end
| Password_sent ->
if Str.string_match cmd_re s 0 then begin
(* Assume the host wants the command: *)
(* Disable now echoing: *)
session # disable_remote_option Telnet_echo;
(* Send the command "ls" 0.1 seconds in the future.
* This way Telnet_echo can be disabled in the
* meantime.
*)
let g = Unixqueue.new_group esys in
Unixqueue.once esys g 0.1
(fun () ->
Queue.add (Telnet_data("ls\n")) oq;
session # update();
state := Command_sent
)
end;
| Command_sent ->
print_string s;
(* Again the command-line prompt? *)
if Str.string_match cmd_re s 0 then
Queue.add Telnet_eof oq; (* terminate the session *)
)
| Telnet_eof ->
()
| _ ->
(* Unexpected command. *)
()
done
in
session # set_event_system esys;
let opts = session # get_options in
session # set_options { opts with
verbose_connection = false;
verbose_input = false;
verbose_output = false };
session # set_connection (Telnet_connect("localhost", 23));
session # enable_remote_option Telnet_suppress_GA;
session # enable_remote_option Telnet_echo;
session # set_callback got_input;
session # attach();
session # run()
;;
next prev parent reply other threads:[~2002-11-06 20:57 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2002-11-06 16:11 Alan Schmitt
2002-11-06 16:26 ` Andrei Errapart
2002-11-06 16:34 ` Alan Schmitt
2002-11-06 17:20 ` Andrei Errapart
2002-11-06 17:39 ` Nicolas George
2002-11-06 19:51 ` David Brown
2002-11-06 20:56 ` Gerd Stolpmann [this message]
2002-11-06 21:07 ` Alan Schmitt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20021106205648.GA1038@ice.gerd-stolpmann.de \
--to=info@gerd-stolpmann.de \
--cc=andreie@no.spam.ee \
--cc=caml-list@inria.fr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox