From mboxrd@z Thu Jan 1 00:00:00 1970 Received: (from majordomo@localhost) by pauillac.inria.fr (8.7.6/8.7.3) id VAA27666; Wed, 6 Nov 2002 21:57:17 +0100 (MET) X-Authentication-Warning: pauillac.inria.fr: majordomo set sender to owner-caml-list@pauillac.inria.fr using -f Received: from concorde.inria.fr (concorde.inria.fr [192.93.2.39]) by pauillac.inria.fr (8.7.6/8.7.3) with ESMTP id VAA28612 for ; Wed, 6 Nov 2002 21:57:16 +0100 (MET) Received: from moutng.kundenserver.de (moutng.kundenserver.de [212.227.126.186]) by concorde.inria.fr (8.11.1/8.11.1) with ESMTP id gA6Kv8518307 for ; Wed, 6 Nov 2002 21:57:13 +0100 (MET) Received: from [212.227.126.160] (helo=mrelayng.kundenserver.de) by moutng.kundenserver.de with esmtp (Exim 3.35 #1) id 189XEQ-0004tQ-00; Wed, 06 Nov 2002 21:56:58 +0100 Received: from [80.129.99.232] (helo=gate.gerd-stolpmann.de) by mrelayng.kundenserver.de with asmtp (Exim 3.35 #1) id 189XEO-00026a-00; Wed, 06 Nov 2002 21:56:56 +0100 Received: from ice.gerd-stolpmann.de (ice.gerd-stolpmann.de [192.168.0.13]) by gate.gerd-stolpmann.de (Postfix) with ESMTP id 6A7F5CCDD; Wed, 6 Nov 2002 21:56:54 +0100 (CET) Received: from ice (localhost [127.0.0.1]) by ice.gerd-stolpmann.de (Postfix) with ESMTP id 5C987B0B7; Wed, 6 Nov 2002 21:56:49 +0100 (CET) Date: Wed, 6 Nov 2002 21:56:48 +0100 From: Gerd Stolpmann To: Andrei Errapart Cc: caml-list@inria.fr Subject: Re: [Caml-list] calling telnet from a caml program Message-ID: <20021106205648.GA1038@ice.gerd-stolpmann.de> References: Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="WIyZ46R2i8wDzkSu" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: ; from andreie@no.spam.ee on Mit, Nov 06, 2002 at 18:20:05 +0100 X-Mailer: Balsa 1.4.1 Sender: owner-caml-list@pauillac.inria.fr Precedence: bulk --WIyZ46R2i8wDzkSu Content-Type: text/plain; charset=ISO-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit 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 ------------------------------------------------------------ --WIyZ46R2i8wDzkSu Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="telnet_client.mli" (* $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. * * *) --WIyZ46R2i8wDzkSu Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="telnet.ml" #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() ;; --WIyZ46R2i8wDzkSu-- ------------------- To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/ Beginner's list: http://groups.yahoo.com/group/ocaml_beginners