%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2003-2009. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %%--------------------------------------------------------------- %% Basic negotiated options with Telnet (RFC 854) %% %% Side A request: I WILL set option Opt. %% Side B answer: DO go ahead, or no, DON'T set it. %% %% Side A request: Please DO set this option. %% Side B answer: Ok I WILL, or no, I WON'T set it. %% %% "Enable option" requests may be rejected. %% "Disable option" requests must not. %%--------------------------------------------------------------- -module(ct_telnet_client). -export([open/1, open/2, open/3, close/1]). -export([send_data/2, get_data/1]). -define(DBG, false). -define(TELNET_PORT, 23). -define(OPEN_TIMEOUT,10000). %% telnet control characters -define(SE, 240). -define(NOP, 241). -define(DM, 242). -define(BRK, 243). -define(IP, 244). -define(AO, 245). -define(AYT, 246). -define(EC, 247). -define(EL, 248). -define(GA, 249). -define(SB, 250). -define(WILL, 251). -define(WONT, 252). -define(DO, 253). -define(DONT, 254). -define(IAC, 255). %% telnet options -define(BINARY, 0). -define(ECHO, 1). -define(SUPPRESS_GO_AHEAD, 3). -define(TERMINAL_TYPE, 24). -define(WINDOW_SIZE, 31). -record(state,{get_data}). open(Server) -> open(Server, ?TELNET_PORT, ?OPEN_TIMEOUT). open(Server, Port) -> open(Server, Port, ?OPEN_TIMEOUT). open(Server, Port, Timeout) -> Self = self(), Pid = spawn(fun() -> init(Self, Server, Port, Timeout) end), receive {open,Pid} -> {ok,Pid}; {Error,Pid} -> Error end. close(Pid) -> Pid ! close. send_data(Pid, Data) -> Pid ! {send_data, Data++"\n"}, ok. get_data(Pid) -> Pid ! {get_data, self()}, receive {data,Data} -> {ok, Data} end. %%%----------------------------------------------------------------- %%% Internal functions init(Parent, Server, Port, Timeout) -> case gen_tcp:connect(Server, Port, [list,{packet,0}], Timeout) of {ok,Sock} -> dbg("Connected to: ~p\n", [Server]), send([?IAC,?DO,?SUPPRESS_GO_AHEAD], Sock), Parent ! {open,self()}, loop(#state{get_data=10}, Sock, []), gen_tcp:close(Sock); Error -> Parent ! {Error,self()} end. loop(State, Sock, Acc) -> receive {tcp_closed,_} -> dbg("Connection closed\n", []), receive {get_data,Pid} -> Pid ! closed after 100 -> ok end; {tcp,_,Msg0} -> dbg("tcp msg: ~p~n",[Msg0]), Msg = check_msg(Sock,Msg0,[]), loop(State, Sock, [Msg | Acc]); {send_data,Data} -> send(Data, Sock), loop(State, Sock, Acc); {get_data,Pid} -> NewState = case Acc of [] -> dbg("get_data nodata\n",[]), erlang:send_after(100,self(),{get_data_delayed,Pid}), State#state{get_data=State#state.get_data - 1}; _ -> Pid ! {data,lists:reverse(lists:append(Acc))}, State end, loop(NewState, Sock, []); {get_data_delayed,Pid} -> NewState = case State#state.get_data of 0 -> send([?IAC,?DO,?NOP], Sock), dbg("delayed after 1000\n",[]), State#state{get_data=10}; _ -> State end, NewAcc = case erlang:is_process_alive(Pid) of true -> Pid ! {data,lists:reverse(lists:append(Acc))}, []; false -> Acc end, loop(NewState, Sock, NewAcc); close -> dbg("Closing connection\n", []), gen_tcp:close(Sock), ok after 1000 -> case Acc of [] -> % no data buffered send([?IAC,?DO,?NOP], Sock), dbg("after 1000\n",[]); _ -> true end, loop(State, Sock, Acc) end. send(Data, Sock) -> dbg("Sending: ~p\n", [Data]), gen_tcp:send(Sock, Data), ok. check_msg(Sock,[?IAC | Cs], Acc) -> case get_cmd(Cs) of {Cmd,Cs1} -> dbg("Got ", []), cmd_dbg(Cmd), respond_cmd(Cmd, Sock), check_msg(Sock, Cs1, Acc); error -> Acc end; check_msg(Sock,[H|T],Acc) -> check_msg(Sock,T,[H|Acc]); check_msg(_Sock,[],Acc) -> Acc. %% Positive responses (WILL and DO). respond_cmd([?WILL,?ECHO], Sock) -> R = [?IAC,?DO,?ECHO], cmd_dbg(R), gen_tcp:send(Sock, R); respond_cmd([?DO,?ECHO], Sock) -> R = [?IAC,?WILL,?ECHO], cmd_dbg(R), gen_tcp:send(Sock, R); %% Answers from server respond_cmd([?WILL,?SUPPRESS_GO_AHEAD], _Sock) -> dbg("Server will suppress-go-ahead\n", []); respond_cmd([?WONT,?SUPPRESS_GO_AHEAD], _Sock) -> dbg("Warning! Server won't suppress-go-ahead\n", []); respond_cmd([?DONT | _Opt], _Sock) -> % server ack? ok; respond_cmd([?WONT | _Opt], _Sock) -> % server ack? ok; %% Negative responses (WON'T and DON'T). These are default! respond_cmd([?WILL,Opt], Sock) -> R = [?IAC,?DONT,Opt], cmd_dbg(R), gen_tcp:send(Sock, R); respond_cmd([?DO | Opt], Sock) -> R = [?IAC,?WONT | Opt], cmd_dbg(R), gen_tcp:send(Sock, R); %% Unexpected messages. respond_cmd([Cmd | Opt], _Sock) when Cmd >= 240, Cmd =< 255 -> dbg("Received cmd: ~w. Ignored!\n", [[Cmd | Opt]]); respond_cmd([Cmd | Opt], _Sock) -> dbg("WARNING: Received unknown cmd: ~w. Ignored!\n", [[Cmd | Opt]]). get_cmd([Cmd | Rest]) when Cmd == ?SB -> get_subcmd(Rest, []); get_cmd([Cmd,Opt | Rest]) -> {[Cmd,Opt], Rest}; get_cmd(_Other) -> error. get_subcmd([?SE | Rest], Acc) -> {[?SE | lists:reverse(Acc)], Rest}; get_subcmd([Opt | Rest], Acc) -> get_subcmd(Rest, [Opt | Acc]). dbg(_Str,_Args) -> ok. % if ?DBG -> io:format(_Str,_Args); % true -> ok % end. cmd_dbg(_Cmd) -> ok. % if ?DBG -> % case _Cmd of % [?IAC|Cmd1] -> % cmd_dbg(Cmd1); % [Ctrl|Opts] -> % CtrlStr = % case Ctrl of % ?DO -> "DO"; % ?DONT -> "DONT"; % ?WILL -> "WILL"; % ?WONT -> "WONT"; % _ -> "CMD" % end, % Opts1 = % case Opts of % [Opt] -> Opt; % _ -> Opts % end, % io:format("~s(~w): ~w\n", [CtrlStr,Ctrl,Opts1]); % Any -> % io:format("Unexpected in cmd_dbg:~n~w~n",[Any]) % end; % true -> ok % end.