#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 Nettelnet_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()
;;
