1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2002-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%%
21
22-module(ftp).
23
24-behaviour(gen_server).
25
26-export([start/0,
27         start_service/1,
28         stop/0,
29         stop_service/1,
30         services/0,
31         service_info/1
32        ]).
33
34%% Added for backward compatibility
35-export([start_standalone/1]).
36
37-export([start_link/1, start_link/2]).
38
39%%  API - Client interface
40-export([cd/2, close/1, delete/2, formaterror/1,
41	 lcd/2, lpwd/1, ls/1, ls/2,
42	 mkdir/2, nlist/1, nlist/2,
43	 open/1, open/2,
44	 pwd/1, quote/2,
45	 recv/2, recv/3, recv_bin/2,
46	 recv_chunk_start/2, recv_chunk/1,
47	 rename/3, rmdir/2,
48	 send/2, send/3, send_bin/3,
49	 send_chunk_start/2, send_chunk/2, send_chunk_end/1,
50	 type/2, user/3, user/4, account/2,
51	 append/3, append/2, append_bin/3,
52	 append_chunk/2, append_chunk_end/1, append_chunk_start/2,
53	 info/1, latest_ctrl_response/1]).
54
55%% gen_server callbacks
56-export([init/1, handle_call/3, handle_cast/2,
57	 handle_info/2, terminate/2, code_change/3]).
58
59-include("ftp_internal.hrl").
60
61%% Constants used in internal state definition
62-define(CONNECTION_TIMEOUT,  60*1000).
63-define(DATA_ACCEPT_TIMEOUT, infinity).
64-define(DEFAULT_MODE,        passive).
65-define(PROGRESS_DEFAULT,    ignore).
66-define(FTP_EXT_DEFAULT,    false).
67
68%% Internal Constants
69-define(FTP_PORT, 21).
70-define(FILE_BUFSIZE, 4096).
71
72
73%%%=========================================================================
74%%%  Data Types
75%%%=========================================================================
76
77%% Internal state
78-record(state, {
79	  csock   = undefined, % socket() - Control connection socket
80	  dsock   = undefined, % socket() - Data connection socket
81	  tls_options = undefined, % list()
82	  verbose = false,   % boolean()
83	  ldir    = undefined,  % string() - Current local directory
84	  type    = ftp_server_default,  % atom() - binary | ascii
85	  chunk   = false,     % boolean() - Receiving data chunks
86	  mode    = ?DEFAULT_MODE,    % passive | active
87	  timeout = ?CONNECTION_TIMEOUT, % integer()
88	  %% Data received so far on the data connection
89	  data    = <<>>,   % binary()
90	  %% Data received so far on the control connection
91	  %% {BinStream, AccLines}. If a binary sequence
92	  %% ends with ?CR then keep it in the binary to
93	  %% be able to detect if the next received byte is ?LF
94	  %% and hence the end of the response is reached!
95	  ctrl_data = {<<>>, [], start},  % {binary(), [bytes()], LineStatus}
96	  %% pid() - Client pid (note not the same as "From")
97	  latest_ctrl_response = "",
98	  owner = undefined,
99	  client = undefined,  % "From" to be used in gen_server:reply/2
100	  %% Function that activated a connection and maybe some
101	  %% data needed further on.
102	  caller = undefined, % term()
103	  ipfamily,     % inet | inet6 | inet6fb4
104          sockopts_ctrl = [],
105          sockopts_data_passive = [],
106          sockopts_data_active = [],
107	  progress = ignore,   % ignore | pid()
108	  dtimeout = ?DATA_ACCEPT_TIMEOUT,  % non_neg_integer() | infinity
109	  tls_upgrading_data_connection = false,
110	  ftp_extension = ?FTP_EXT_DEFAULT
111	 }).
112
113-record(recv_chunk_closing, {
114          dconn_closed = false,
115          pos_compl_received = false,
116          client_called_us = false
117         }).
118
119
120-type shortage_reason()  :: 'etnospc' | 'epnospc'.
121-type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'.
122-type common_reason() ::  'econn' | 'eclosed' | term().
123-type file_write_error_reason() :: term(). % See file:write for more info
124
125-define(DBG(F,A), 'n/a').
126%%-define(DBG(F,A), io:format(F,A)).
127%%-define(DBG(F,A), ct:pal("~p:~p " ++ if is_list(F) -> F; is_atom(F) -> atom_to_list(F) end, [?MODULE,?LINE|A])).
128
129
130%%%=========================================================================
131%%%  API
132%%%=========================================================================
133
134start() ->
135    application:start(ftp).
136
137start_standalone(Options) ->
138    try
139	{ok, StartOptions} = start_options(Options),
140	{ok, OpenOptions}  = open_options(Options),
141	{ok, SocketOptions}  = socket_options(Options),
142	case start_link(StartOptions, []) of
143	    {ok, Pid} ->
144		call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain);
145	    Error1 ->
146		Error1
147	end
148    catch
149	throw:Error2 ->
150	    Error2
151    end.
152
153start_service(Options) ->
154    try
155	{ok, StartOptions} = start_options(Options),
156	{ok, OpenOptions}  = open_options(Options),
157	{ok, SocketOptions}  = socket_options(Options),
158	case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
159	    {ok, Pid} ->
160		call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain);
161	    Error1 ->
162		Error1
163	end
164    catch
165	throw:Error2 ->
166	    Error2
167    end.
168
169stop() ->
170    application:stop(ftp).
171
172stop_service(Pid) ->
173    close(Pid).
174
175services() ->
176    [{ftpc, Pid} || {_, Pid, _, _} <-
177			supervisor:which_children(ftp_sup)].
178service_info(Pid) ->
179    {ok, Info} = call(Pid, info, list),
180    {ok, [proplists:lookup(mode, Info),
181	  proplists:lookup(local_port, Info),
182	  proplists:lookup(peer, Info),
183	  proplists:lookup(peer_port, Info)]}.
184
185
186%%%=========================================================================
187%%%  API - CLIENT FUNCTIONS
188%%%=========================================================================
189
190%%--------------------------------------------------------------------------
191%% open(HostOrOtpList, <Port>, <Flags>) -> {ok, Pid} | {error, ehost}
192%%	HostOrOtpList = string() | [{option_list, Options}]
193%%      Port = integer(),
194%%      Flags = [Flag],
195%%      Flag = verbose | debug | trace
196%%
197%% Description:  Start an ftp client and connect to a host.
198%%--------------------------------------------------------------------------
199
200-spec open(Host :: string() | inet:ip_address()) ->
201    {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
202
203%% <BACKWARD-COMPATIBILLITY>
204open({option_list, Options}) when is_list(Options) ->
205    try
206	{ok, StartOptions} = start_options(Options),
207	{ok, OpenOptions}  = open_options(Options),
208	{ok, SockOpts}  = socket_options(Options),
209	case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of
210	    {ok, Pid} ->
211		call(Pid, {open, ip_comm, OpenOptions, SockOpts}, plain);
212	    Error1 ->
213		Error1
214	end
215    catch
216	throw:Error2 ->
217	    Error2
218    end;
219%% </BACKWARD-COMPATIBILLITY>
220
221open(Host) ->
222    open(Host, []).
223
224-spec open(Host :: string() | inet:ip_address(), Opts :: list()) ->
225    {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
226
227%% <BACKWARD-COMPATIBILLITY>
228open(Host, Port) when is_integer(Port) ->
229    open(Host, [{port, Port}]);
230%% </BACKWARD-COMPATIBILLITY>
231
232open(Host, Opts) when is_list(Opts) ->
233    try
234	{ok, StartOptions} = start_options(Opts),
235	{ok, OpenOptions}  = open_options([{host, Host}|Opts]),
236	{ok, SocketOptions}  = socket_options(Opts),
237	case start_link(StartOptions, []) of
238	    {ok, Pid} ->
239		do_open(Pid, OpenOptions, SocketOptions, tls_options(Opts));
240	    Error1 ->
241		Error1
242	end
243    catch
244	throw:Error2 ->
245	    Error2
246    end.
247
248do_open(Pid, OpenOptions, SocketOptions, TLSOpts) ->
249    case call(Pid, {open, ip_comm, OpenOptions, SocketOptions}, plain) of
250	{ok, Pid} ->
251	    maybe_tls_upgrade(Pid, TLSOpts);
252	Error ->
253	    Error
254    end.
255%%--------------------------------------------------------------------------
256%% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn}
257%%                                    | {error, eacct}
258%%	Pid = pid(),
259%%      User = Pass =  Acc = string()
260%%
261%% Description:  Login with or without a supplied account name.
262%%--------------------------------------------------------------------------
263-spec user(Pid  :: pid(),
264	   User :: string(),
265	   Pass :: string()) ->
266    'ok' | {'error', Reason :: 'euser' | common_reason()}.
267
268user(Pid, User, Pass) ->
269    case {is_name_sane(User), is_name_sane(Pass)} of
270	{true, true} ->
271	    call(Pid, {user, User, Pass}, atom);
272	_ ->
273	    {error, euser}
274    end.
275
276-spec user(Pid  :: pid(),
277	   User :: string(),
278	   Pass :: string(),
279	   Acc  :: string()) ->
280    'ok' | {'error', Reason :: 'euser' | common_reason()}.
281
282user(Pid, User, Pass, Acc) ->
283    case {is_name_sane(User), is_name_sane(Pass), is_name_sane(Acc)} of
284	{true, true, true} ->
285	    call(Pid, {user, User, Pass, Acc}, atom);
286	_ ->
287	    {error, euser}
288    end.
289
290
291%%--------------------------------------------------------------------------
292%% account(Pid, Acc)  -> ok | {error, eacct}
293%%	Pid = pid()
294%%	Acc= string()
295%%
296%% Description:  Set a user Account.
297%%--------------------------------------------------------------------------
298
299-spec account(Pid :: pid(), Acc :: string()) ->
300    'ok' | {'error', Reason :: 'eacct' | common_reason()}.
301
302account(Pid, Acc) ->
303    case is_name_sane(Acc) of
304	true ->
305	    call(Pid, {account, Acc}, atom);
306	_ ->
307	    {error, eacct}
308    end.
309
310
311%%--------------------------------------------------------------------------
312%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn}
313%%	Pid = pid()
314%%      Dir = string()
315%%
316%% Description:  Get the current working directory at remote server.
317%%--------------------------------------------------------------------------
318
319-spec pwd(Pid :: pid()) ->
320    {'ok', Dir :: string()} |
321	{'error', Reason :: restriction_reason() | common_reason()}.
322
323pwd(Pid) ->
324    call(Pid, pwd, ctrl).
325
326
327%%--------------------------------------------------------------------------
328%% lpwd(Pid) ->  {ok, Dir}
329%%	Pid = pid()
330%%      Dir = string()
331%%
332%% Description:  Get the current working directory at local server.
333%%--------------------------------------------------------------------------
334
335-spec lpwd(Pid :: pid()) ->
336    {'ok', Dir :: string()}.
337
338lpwd(Pid) ->
339    call(Pid, lpwd, string).
340
341
342%%--------------------------------------------------------------------------
343%% cd(Pid, Dir) ->  ok | {error, epath} | {error, elogin} | {error, econn}
344%%	Pid = pid()
345%%	Dir = string()
346%%
347%% Description:  Change current working directory at remote server.
348%%--------------------------------------------------------------------------
349
350-spec cd(Pid :: pid(), Dir :: string()) ->
351    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
352
353cd(Pid, Dir) ->
354    case is_name_sane(Dir) of
355	true ->
356	    call(Pid, {cd, Dir}, atom);
357	_ ->
358	    {error, efnamena}
359    end.
360
361
362%%--------------------------------------------------------------------------
363%% lcd(Pid, Dir) ->  ok | {error, epath}
364%%	Pid = pid()
365%%	Dir = string()
366%%
367%% Description:  Change current working directory for the local client.
368%%--------------------------------------------------------------------------
369
370-spec lcd(Pid :: pid(), Dir :: string()) ->
371    'ok' | {'error', Reason :: restriction_reason()}.
372
373lcd(Pid, Dir) ->
374    call(Pid, {lcd, Dir}, string).
375
376
377%%--------------------------------------------------------------------------
378%% ls(Pid) -> Result
379%% ls(Pid, <Dir>) -> Result
380%%
381%%	Pid = pid()
382%%	Dir = string()
383%%      Result = {ok, Listing} | {error, Reason}
384%%      Listing = string()
385%%      Reason = epath | elogin | econn
386%%
387%% Description: Returns a list of files in long format.
388%%--------------------------------------------------------------------------
389
390-spec ls(Pid :: pid()) ->
391    {'ok', Listing :: string()} |
392	{'error', Reason :: restriction_reason() | common_reason()}.
393
394ls(Pid) ->
395  ls(Pid, "").
396
397-spec ls(Pid :: pid(), Dir :: string()) ->
398    {'ok', Listing :: string()} |
399	{'error', Reason ::  restriction_reason() | common_reason()}.
400
401ls(Pid, Dir) ->
402    case is_name_sane(Dir) of
403	true ->
404	    call(Pid, {dir, long, Dir}, string);
405	_ ->
406	    {error, efnamena}
407    end.
408
409
410%%--------------------------------------------------------------------------
411%% nlist(Pid) -> Result
412%% nlist(Pid, Pathname) -> Result
413%%
414%%	Pid = pid()
415%%	Pathname = string()
416%%      Result = {ok, Listing} | {error, Reason}
417%%      Listing = string()
418%%      Reason = epath | elogin | econn
419%%
420%% Description:  Returns a list of files in short format
421%%--------------------------------------------------------------------------
422
423-spec nlist(Pid :: pid()) ->
424    {'ok', Listing :: string()} |
425	{'error', Reason :: restriction_reason() | common_reason()}.
426
427nlist(Pid) ->
428  nlist(Pid, "").
429
430-spec nlist(Pid :: pid(), Pathname :: string()) ->
431    {'ok', Listing :: string()} |
432	{'error', Reason :: restriction_reason() | common_reason()}.
433
434nlist(Pid, Dir) ->
435    case is_name_sane(Dir) of
436	true ->
437	    call(Pid, {dir, short, Dir}, string);
438	_ ->
439	    {error, efnamena}
440    end.
441
442
443%%--------------------------------------------------------------------------
444%% rename(Pid, Old, New) ->  ok | {error, epath} | {error, elogin}
445%%                              | {error, econn}
446%%	Pid = pid()
447%%	CurrFile = NewFile = string()
448%%
449%% Description:  Rename a file at remote server.
450%%--------------------------------------------------------------------------
451
452-spec rename(Pid :: pid(), Old :: string(), New :: string()) ->
453    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
454
455rename(Pid, Old, New) ->
456    case {is_name_sane(Old), is_name_sane(New)} of
457	{true, true} ->
458	    call(Pid, {rename, Old, New}, string);
459	_ ->
460	    {error, efnamena}
461    end.
462
463
464%%--------------------------------------------------------------------------
465%% delete(Pid, File) ->  ok | {error, epath} | {error, elogin} |
466%%                       {error, econn}
467%%	Pid = pid()
468%%	File = string()
469%%
470%% Description:  Remove file at remote server.
471%%--------------------------------------------------------------------------
472
473-spec delete(Pid :: pid(), File :: string()) ->
474    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
475
476delete(Pid, File) ->
477    case is_name_sane(File) of
478	true ->
479	    call(Pid, {delete, File}, string);
480	_ ->
481	    {error, efnamena}
482    end.
483
484
485%%--------------------------------------------------------------------------
486%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
487%%	Pid = pid(),
488%%	Dir = string()
489%%
490%% Description:  Make directory at remote server.
491%%--------------------------------------------------------------------------
492
493-spec mkdir(Pid :: pid(), Dir :: string()) ->
494    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
495
496mkdir(Pid, Dir) ->
497    case is_name_sane(Dir) of
498	true ->
499	    call(Pid, {mkdir, Dir}, atom);
500	_ ->
501	    {error, efnamena}
502    end.
503
504
505%%--------------------------------------------------------------------------
506%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn}
507%%	Pid = pid(),
508%%	Dir = string()
509%%
510%% Description:  Remove directory at remote server.
511%%--------------------------------------------------------------------------
512
513-spec rmdir(Pid :: pid(), Dir :: string()) ->
514    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
515
516rmdir(Pid, Dir) ->
517    case is_name_sane(Dir) of
518	true ->
519	    call(Pid, {rmdir, Dir}, atom);
520	_ ->
521	    {error, efnamena}
522    end.
523
524
525%%--------------------------------------------------------------------------
526%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn}
527%%	Pid = pid()
528%%	Type = ascii | binary
529%%
530%% Description:  Set transfer type.
531%%--------------------------------------------------------------------------
532
533-spec type(Pid :: pid(), Type :: ascii | binary) ->
534    'ok' |
535	{'error', Reason :: 'etype' | restriction_reason() | common_reason()}.
536
537type(Pid, Type) ->
538    call(Pid, {type, Type}, atom).
539
540
541%%--------------------------------------------------------------------------
542%% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} |
543%%                                          {error, elogin} | {error, econn}
544%%	Pid = pid()
545%%	RemoteFileName = LocalFileName = string()
546%%
547%% Description:  Transfer file from remote server.
548%%--------------------------------------------------------------------------
549
550-spec recv(Pid :: pid(), RemoteFileName :: string()) ->
551    'ok' | {'error', Reason :: restriction_reason() |
552                               common_reason() |
553                               file_write_error_reason()}.
554
555recv(Pid, RemotFileName) ->
556  recv(Pid, RemotFileName, RemotFileName).
557
558-spec recv(Pid            :: pid(),
559	   RemoteFileName :: string(),
560	   LocalFileName  :: string()) ->
561    'ok' | {'error', Reason :: term()}.
562
563recv(Pid, RemotFileName, LocalFileName) ->
564    case is_name_sane(RemotFileName) of
565	true ->
566	    call(Pid, {recv, RemotFileName, LocalFileName}, atom);
567	_ ->
568	    {error, efnamena}
569    end.
570
571
572%%--------------------------------------------------------------------------
573%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin}
574%%			   | {error, econn}
575%%	Pid = pid()
576%%	RemoteFile = string()
577%%      Bin = binary()
578%%
579%% Description:  Transfer file from remote server into binary.
580%%--------------------------------------------------------------------------
581
582-spec recv_bin(Pid        :: pid(),
583	       RemoteFile :: string()) ->
584    {'ok', Bin :: binary()} |
585	{'error', Reason :: restriction_reason() | common_reason()}.
586
587recv_bin(Pid, RemoteFile) ->
588    case is_name_sane(RemoteFile) of
589	true ->
590	    call(Pid, {recv_bin, RemoteFile}, bin);
591	_ ->
592	    {error, efnamena}
593    end.
594
595
596%%--------------------------------------------------------------------------
597%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
598%%                                 | {error, econn}
599%%	Pid = pid()
600%%	RemoteFile = string()
601%%
602%% Description:  Start receive of chunks of remote file.
603%%--------------------------------------------------------------------------
604
605-spec recv_chunk_start(Pid        :: pid(),
606		       RemoteFile :: string()) ->
607    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
608
609recv_chunk_start(Pid, RemoteFile) ->
610    case is_name_sane(RemoteFile) of
611	true ->
612	    call(Pid, {recv_chunk_start, RemoteFile}, atom);
613	_ ->
614	    {error, efnamena}
615    end.
616
617
618%%--------------------------------------------------------------------------
619%% recv_chunk(Pid, RemoteFile) ->  ok | {ok, Bin} | {error, Reason}
620%%	Pid = pid()
621%%	RemoteFile = string()
622%%
623%% Description:  Transfer file from remote server into binary in chunks
624%%--------------------------------------------------------------------------
625
626-spec recv_chunk(Pid :: pid()) ->
627    'ok' |
628	{'ok', Bin :: binary()} |
629	{'error', Reason :: restriction_reason() | common_reason()}.
630
631recv_chunk(Pid) ->
632    call(Pid, recv_chunk, atom).
633
634
635%%--------------------------------------------------------------------------
636%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
637%%                                                  | {error, elogin}
638%%                                                  | {error, econn}
639%%	Pid = pid()
640%%	LocalFileName = RemotFileName = string()
641%%
642%% Description:  Transfer file to remote server.
643%%--------------------------------------------------------------------------
644
645-spec send(Pid :: pid(), LocalFileName :: string()) ->
646    'ok' |
647	{'error', Reason :: restriction_reason() |
648                            common_reason() |
649                            shortage_reason()}.
650
651send(Pid, LocalFileName) ->
652  send(Pid, LocalFileName, LocalFileName).
653
654-spec send(Pid            :: pid(),
655	   LocalFileName  :: string(),
656	   RemoteFileName :: string()) ->
657    'ok' |
658	{'error', Reason :: restriction_reason() |
659                            common_reason() |
660                            shortage_reason()}.
661
662send(Pid, LocalFileName, RemotFileName) ->
663    case is_name_sane(RemotFileName) of
664	true ->
665	    call(Pid, {send, LocalFileName, RemotFileName}, atom);
666	_ ->
667	    {error, efnamena}
668    end.
669
670
671%%--------------------------------------------------------------------------
672%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
673%%                             | {error, enotbinary} | {error, econn}
674%%	Pid = pid()
675%%	Bin = binary()
676%%	RemoteFile = string()
677%%
678%% Description:  Transfer a binary to a remote file.
679%%--------------------------------------------------------------------------
680
681-spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) ->
682    'ok' |
683	{'error', Reason :: restriction_reason() |
684                            common_reason() |
685                            shortage_reason()}.
686
687send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
688    case is_name_sane(RemoteFile) of
689	true ->
690	    call(Pid, {send_bin, Bin, RemoteFile}, atom);
691	_ ->
692	    {error, efnamena}
693    end;
694send_bin(_Pid, _Bin, _RemoteFile) ->
695  {error, enotbinary}.
696
697
698%%--------------------------------------------------------------------------
699%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath}
700%%                                 | {error, econn}
701%%	Pid = pid()
702%%	RemoteFile = string()
703%%
704%% Description:  Start transfer of chunks to remote file.
705%%--------------------------------------------------------------------------
706
707-spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
708    'ok' | {'error', Reason :: restriction_reason() | common_reason()}.
709
710send_chunk_start(Pid, RemoteFile) ->
711    case is_name_sane(RemoteFile) of
712	true ->
713	    call(Pid, {send_chunk_start, RemoteFile}, atom);
714	_ ->
715	    {error, efnamena}
716    end.
717
718
719%%--------------------------------------------------------------------------
720%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} |
721%%                                        {error, epath} | {error, econn}
722%%	Pid = pid()
723%%	RemoteFile = string()
724%%
725%% Description:  Start append chunks of data to remote file.
726%%--------------------------------------------------------------------------
727
728-spec append_chunk_start(Pid :: pid(), RemoteFile :: string()) ->
729    'ok' | {'error', Reason :: term()}.
730
731append_chunk_start(Pid, RemoteFile) ->
732    case is_name_sane(RemoteFile) of
733	true ->
734	    call(Pid, {append_chunk_start, RemoteFile}, atom);
735	_ ->
736	    {error, efnamena}
737    end.
738
739
740%%--------------------------------------------------------------------------
741%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
742%%                       | {error, echunk} | {error, econn}
743%%      Pid = pid()
744%%	Bin = binary().
745%%
746%% Purpose:  Send chunk to remote file.
747%%--------------------------------------------------------------------------
748
749-spec send_chunk(Pid :: pid(), Bin :: binary()) ->
750    'ok' |
751	{'error', Reason :: 'echunk' |
752                            restriction_reason() |
753                            common_reason()}.
754
755send_chunk(Pid, Bin) when is_binary(Bin) ->
756    call(Pid, {transfer_chunk, Bin}, atom);
757send_chunk(_Pid, _Bin) ->
758  {error, enotbinary}.
759
760
761%%--------------------------------------------------------------------------
762%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary}
763%%			     | {error, echunk} | {error, econn}
764%%	Pid = pid()
765%%	Bin = binary()
766%%
767%% Description:  Append chunk to remote file.
768%%--------------------------------------------------------------------------
769
770-spec append_chunk(Pid :: pid(), Bin :: binary()) ->
771    'ok' |
772	{'error', Reason :: 'echunk' |
773                            restriction_reason() |
774                            common_reason()}.
775
776append_chunk(Pid, Bin) when is_binary(Bin) ->
777    call(Pid, {transfer_chunk, Bin}, atom);
778append_chunk(_Pid, _Bin) ->
779  {error, enotbinary}.
780
781
782%%--------------------------------------------------------------------------
783%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk}
784%%			  | {error, econn}
785%%	Pid = pid()
786%%
787%% Description:  End sending of chunks to remote file.
788%%--------------------------------------------------------------------------
789
790-spec send_chunk_end(Pid :: pid()) ->
791    'ok' |
792	{'error', Reason :: restriction_reason() |
793                            common_reason() |
794                            shortage_reason()}.
795
796send_chunk_end(Pid) ->
797    call(Pid, chunk_end, atom).
798
799
800%%--------------------------------------------------------------------------
801%% append_chunk_end(Pid) ->  ok | {error, elogin} | {error, echunk}
802%%			     | {error, econn}
803%%	Pid = pid()
804%%
805%% Description:  End appending of chunks to remote file.
806%%--------------------------------------------------------------------------
807
808-spec append_chunk_end(Pid :: pid()) ->
809    'ok' |
810	{'error', Reason :: restriction_reason() |
811                            common_reason() |
812                            shortage_reason()}.
813
814append_chunk_end(Pid) ->
815    call(Pid, chunk_end, atom).
816
817
818%%--------------------------------------------------------------------------
819%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath}
820%%                                                    | {error, elogin}
821%%                                                    | {error, econn}
822%%	Pid = pid()
823%%	LocalFileName = RemotFileName = string()
824%%
825%% Description:  Append the local file to the remote file
826%%--------------------------------------------------------------------------
827
828-spec append(Pid :: pid(), LocalFileName :: string()) ->
829    'ok' |
830	{'error', Reason :: 'epath'    |
831                            'elogin'   |
832                            'etnospc'  |
833                            'epnospc'  |
834                            'efnamena' | common_reason()}.
835
836append(Pid, LocalFileName) ->
837    append(Pid, LocalFileName, LocalFileName).
838
839-spec append(Pid            :: pid(),
840	     LocalFileName  :: string(),
841	     RemoteFileName :: string()) ->
842    'ok' | {'error', Reason :: term()}.
843
844append(Pid, LocalFileName, RemotFileName) ->
845    case is_name_sane(RemotFileName) of
846	true ->
847	    call(Pid, {append, LocalFileName, RemotFileName}, atom);
848	_ ->
849	    {error, efnamena}
850    end.
851
852
853%%--------------------------------------------------------------------------
854%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin}
855%%				  | {error, enotbinary} | {error, econn}
856%%	Pid = pid()
857%%	Bin = binary()
858%%	RemoteFile = string()
859%%
860%% Purpose:  Append a binary to a remote file.
861%%--------------------------------------------------------------------------
862
863-spec append_bin(Pid        :: pid(),
864		 Bin        :: binary(),
865		 RemoteFile :: string()) ->
866    'ok' |
867	{'error', Reason :: restriction_reason() |
868                            common_reason() |
869                            shortage_reason()}.
870
871append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) ->
872    case is_name_sane(RemoteFile) of
873	true ->
874	    call(Pid, {append_bin, Bin, RemoteFile}, atom);
875	_ ->
876	    {error, efnamena}
877    end;
878append_bin(_Pid, _Bin, _RemoteFile) ->
879    {error, enotbinary}.
880
881
882%%--------------------------------------------------------------------------
883%% quote(Pid, Cmd) -> list()
884%%	Pid = pid()
885%%	Cmd = string()
886%%
887%% Description: Send arbitrary ftp command.
888%%--------------------------------------------------------------------------
889
890-spec quote(Pid :: pid(), Cmd :: string()) -> list().
891
892quote(Pid, Cmd) when is_list(Cmd) ->
893    call(Pid, {quote, Cmd}, atom).
894
895
896%%--------------------------------------------------------------------------
897%% close(Pid) -> ok
898%%	Pid = pid()
899%%
900%% Description:  End the ftp session.
901%%--------------------------------------------------------------------------
902
903-spec close(Pid :: pid()) -> 'ok'.
904
905close(Pid) ->
906    cast(Pid, close),
907    ok.
908
909
910%%--------------------------------------------------------------------------
911%% formaterror(Tag) -> string()
912%%	Tag = atom() | {error, atom()}
913%%
914%% Description:  Return diagnostics.
915%%--------------------------------------------------------------------------
916
917-spec formaterror(Tag :: term()) -> string().
918
919formaterror(Tag) ->
920  ftp_response:error_string(Tag).
921
922
923info(Pid) ->
924    call(Pid, info, list).
925
926
927%%--------------------------------------------------------------------------
928%% latest_ctrl_response(Pid) -> string()
929%%	Pid = pid()
930%%
931%% Description:  The latest received response from the server
932%%--------------------------------------------------------------------------
933
934-spec latest_ctrl_response(Pid :: pid()) -> string().
935
936latest_ctrl_response(Pid) ->
937    call(Pid, latest_ctrl_response, string).
938
939
940%%%========================================================================
941%%% gen_server callback functions
942%%%========================================================================
943
944%%-------------------------------------------------------------------------
945%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason}
946%% Description: Initiates the erlang process that manages a ftp connection.
947%%-------------------------------------------------------------------------
948init(Options) ->
949    process_flag(trap_exit, true),
950
951    %% Keep track of the client
952    {value, {client, Client}} = lists:keysearch(client, 1, Options),
953    erlang:monitor(process, Client),
954
955    %% Make sure inet is started
956    _ = inet_db:start(),
957
958    %% Where are we
959    {ok, Dir} = file:get_cwd(),
960
961    %% Maybe activate dbg
962    case key_search(debug, Options, disable) of
963	trace ->
964	    dbg:tracer(),
965	    dbg:p(all, [call]),
966	    {ok, _} = dbg:tpl(ftp, [{'_', [], [{return_trace}]}]),
967	    {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]),
968	    {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]),
969	    ok;
970	debug ->
971	    dbg:tracer(),
972	    dbg:p(all, [call]),
973	    {ok, _} = dbg:tp(ftp, [{'_', [], [{return_trace}]}]),
974	    {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]),
975	    {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]),
976	    ok;
977	_ ->
978	    %% Keep silent
979	    ok
980    end,
981
982    %% Verbose?
983    Verbose  = key_search(verbose, Options, false),
984
985    %% IpFamily?
986    IpFamily = key_search(ipfamily, Options, inet),
987
988    State    = #state{owner    = Client,
989		      verbose  = Verbose,
990		      ipfamily = IpFamily,
991		      ldir     = Dir},
992
993    %% Set process prio
994    Priority = key_search(priority, Options, low),
995    process_flag(priority, Priority),
996
997    %% And we are done
998    {ok, State}.
999
1000
1001%%--------------------------------------------------------------------------
1002%% handle_call(Request, From, State) -> {reply, Reply, State} |
1003%%                                      {reply, Reply, State, Timeout} |
1004%%                                      {noreply, State}               |
1005%%                                      {noreply, State, Timeout}      |
1006%%                                      {stop, Reason, Reply, State}   |
1007%% Description: Handle incoming requests.
1008%%-------------------------------------------------------------------------
1009
1010%% Anyone can ask this question
1011handle_call({_, info}, _, #state{verbose  = Verbose,
1012				 mode     = Mode,
1013				 timeout  = Timeout,
1014				 ipfamily = IpFamily,
1015				 csock    = Socket,
1016				 progress = Progress} = State) ->
1017    {ok, {_, LocalPort}}  = sockname(Socket),
1018    {ok, {Address, Port}} = peername(Socket),
1019    Options = [{verbose,    Verbose},
1020	       {ipfamily,   IpFamily},
1021	       {mode,       Mode},
1022	       {peer,       Address},
1023	       {peer_port,  Port},
1024	       {local_port, LocalPort},
1025	       {timeout,    Timeout},
1026	       {progress,   Progress}],
1027    {reply, {ok, Options}, State};
1028
1029handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = State) ->
1030    {reply, {ok,Resp}, State};
1031
1032%% But everything else must come from the owner
1033handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid ->
1034    {reply, {error, not_connection_owner}, State};
1035
1036handle_call({_, {open, ip_comm, Opts, {CtrlOpts, DataPassOpts, DataActOpts}}}, From, State) ->
1037    case key_search(host, Opts, undefined) of
1038	undefined ->
1039	    {stop, normal, {error, ehost}, State};
1040	Host ->
1041	    Mode     = key_search(mode,     Opts, ?DEFAULT_MODE),
1042	    Port     = key_search(port,     Opts, ?FTP_PORT),
1043	    Timeout  = key_search(timeout,  Opts, ?CONNECTION_TIMEOUT),
1044	    DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT),
1045	    Progress = key_search(progress, Opts, ignore),
1046	    IpFamily = key_search(ipfamily, Opts, inet),
1047	    FtpExt   = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT),
1048
1049	    State2 = State#state{client   = From,
1050				 mode     = Mode,
1051				 progress = progress(Progress),
1052				 ipfamily = IpFamily,
1053                                 sockopts_ctrl = CtrlOpts,
1054                                 sockopts_data_passive =  DataPassOpts,
1055                                 sockopts_data_active = DataActOpts,
1056				 dtimeout = DTimeout,
1057				 ftp_extension = FtpExt},
1058
1059	    case setup_ctrl_connection(Host, Port, Timeout, State2) of
1060		{ok, State3, WaitTimeout} ->
1061		    {noreply, State3, WaitTimeout};
1062		{error, _Reason} ->
1063		    gen_server:reply(From, {error, ehost}),
1064		    {stop, normal, State2#state{client = undefined}}
1065	    end
1066    end;
1067
1068handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State0) ->
1069    _ = send_ctrl_message(State0, mk_cmd("AUTH TLS", [])),
1070    State = activate_ctrl_connection(State0),
1071    {noreply, State#state{client = From, caller = open, tls_options = TLSOptions}};
1072
1073handle_call({_, {user, User, Password}}, From,
1074	    #state{csock = CSock} = State) when (CSock =/= undefined) ->
1075    handle_user(User, Password, "", State#state{client = From});
1076
1077handle_call({_, {user, User, Password, Acc}}, From,
1078	    #state{csock = CSock} = State) when (CSock =/= undefined) ->
1079    handle_user(User, Password, Acc, State#state{client = From});
1080
1081handle_call({_, {account, Acc}}, From, State)->
1082    handle_user_account(Acc, State#state{client = From});
1083
1084handle_call({_, pwd}, From, #state{chunk = false} = State0) ->
1085    _ = send_ctrl_message(State0, mk_cmd("PWD", [])),
1086    State = activate_ctrl_connection(State0),
1087    {noreply, State#state{client = From, caller = pwd}};
1088
1089handle_call({_, lpwd}, From,  #state{ldir = LDir} = State) ->
1090    {reply, {ok, LDir}, State#state{client = From}};
1091
1092handle_call({_, {cd, Dir}}, From,  #state{chunk = false} = State0) ->
1093    _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
1094    State = activate_ctrl_connection(State0),
1095    {noreply, State#state{client = From, caller = cd}};
1096
1097handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) ->
1098    LDir = filename:absname(Dir, LDir0),
1099    case file:read_file_info(LDir) of %% FIX better check that LDir is a dir.
1100	{ok, _ } ->
1101	    {reply, ok, State#state{ldir = LDir}};
1102	_  ->
1103	    {reply, {error, epath}, State}
1104    end;
1105
1106handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From,
1107	    #state{chunk = false} = State) ->
1108    setup_data_connection(State#state{caller = {dir, Dir, Len},
1109				      client = From});
1110handle_call({_, {rename, CurrFile, NewFile}}, From,
1111	    #state{chunk = false} = State0) ->
1112    _ = send_ctrl_message(State0, mk_cmd("RNFR ~s", [CurrFile])),
1113    State = activate_ctrl_connection(State0),
1114    {noreply, State#state{caller = {rename, NewFile}, client = From}};
1115
1116handle_call({_, {delete, File}}, {_Pid, _} = From,
1117	    #state{chunk = false} = State0) ->
1118    _ = send_ctrl_message(State0, mk_cmd("DELE ~s", [File])),
1119    State = activate_ctrl_connection(State0),
1120    {noreply, State#state{client = From}};
1121
1122handle_call({_, {mkdir, Dir}}, From,  #state{chunk = false} = State0) ->
1123    _ = send_ctrl_message(State0, mk_cmd("MKD ~s", [Dir])),
1124    State = activate_ctrl_connection(State0),
1125    {noreply, State#state{client = From}};
1126
1127handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State0) ->
1128    _ = send_ctrl_message(State0, mk_cmd("RMD ~s", [Dir])),
1129    State = activate_ctrl_connection(State0),
1130    {noreply, State#state{client = From}};
1131
1132handle_call({_,{type, Type}}, From, #state{chunk = false} = State0) ->
1133    case Type of
1134	ascii ->
1135	    _ = send_ctrl_message(State0, mk_cmd("TYPE A", [])),
1136	    State = activate_ctrl_connection(State0),
1137	    {noreply, State#state{caller = type, type = ascii,
1138				  client = From}};
1139	binary ->
1140	    _ = send_ctrl_message(State0, mk_cmd("TYPE I", [])),
1141	    State = activate_ctrl_connection(State0),
1142	    {noreply, State#state{caller = type, type = binary,
1143				  client = From}};
1144	_ ->
1145	    {reply, {error, etype}, State0}
1146    end;
1147
1148handle_call({_,{recv, RemoteFile, LocalFile}}, From,
1149	    #state{chunk = false, ldir = LocalDir} = State) ->
1150    progress_report({remote_file, RemoteFile}, State),
1151    NewLocalFile = filename:absname(LocalFile, LocalDir),
1152
1153    case file_open(NewLocalFile, write) of
1154	{ok, Fd} ->
1155	    setup_data_connection(State#state{client = From,
1156					      caller =
1157					      {recv_file,
1158					       RemoteFile, Fd}});
1159	{error, _What} ->
1160	    {reply, {error, epath}, State}
1161    end;
1162
1163handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} =
1164	    State) ->
1165    setup_data_connection(State#state{caller = {recv_bin, RemoteFile},
1166				      client = From});
1167
1168handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false}
1169	    = State) ->
1170    setup_data_connection(State#state{caller = {start_chunk_transfer,
1171						"RETR", RemoteFile},
1172				      client = From});
1173
1174handle_call({_, recv_chunk}, _, #state{chunk = false} = State) ->
1175    {reply, {error, "ftp:recv_chunk_start/2 not called"}, State};
1176
1177handle_call({_, recv_chunk}, _From, #state{chunk = true,
1178                                           caller = #recv_chunk_closing{dconn_closed       = true,
1179                                                                        pos_compl_received = true
1180                                                                       }
1181                                          } = State0) ->
1182    %% The ftp:recv_chunk call was the last event we waited for, finnish and clean up
1183    ?DBG("recv_chunk_closing ftp:recv_chunk, last event",[]),
1184    State = activate_ctrl_connection(State0),
1185    {reply, ok, State#state{caller = undefined,
1186                             chunk = false,
1187                             client = undefined}};
1188
1189handle_call({_, recv_chunk}, From, #state{chunk = true,
1190                                          caller = #recv_chunk_closing{} = R
1191                                         } = State) ->
1192    %% Waiting for more, don't care what
1193    ?DBG("recv_chunk_closing ftp:recv_chunk, get more",[]),
1194    {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}};
1195
1196handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) ->
1197    State = activate_data_connection(State0),
1198    {noreply, State#state{client = From, caller = recv_chunk}};
1199
1200handle_call({_, {send, LocalFile, RemoteFile}}, From,
1201	    #state{chunk = false, ldir = LocalDir} = State) ->
1202    progress_report({local_file, filename:absname(LocalFile, LocalDir)},
1203		    State),
1204    setup_data_connection(State#state{caller = {transfer_file,
1205						   {"STOR",
1206						    LocalFile, RemoteFile}},
1207					 client = From});
1208handle_call({_, {append, LocalFile, RemoteFile}}, From,
1209	    #state{chunk = false} = State) ->
1210    setup_data_connection(State#state{caller = {transfer_file,
1211						{"APPE",
1212						 LocalFile, RemoteFile}},
1213				      client = From});
1214handle_call({_, {send_bin, Bin, RemoteFile}}, From,
1215	    #state{chunk = false} = State) ->
1216    setup_data_connection(State#state{caller = {transfer_data,
1217					       {"STOR", Bin, RemoteFile}},
1218				      client = From});
1219handle_call({_,{append_bin, Bin, RemoteFile}}, From,
1220	    #state{chunk = false} = State) ->
1221    setup_data_connection(State#state{caller = {transfer_data,
1222						{"APPE", Bin, RemoteFile}},
1223				      client = From});
1224handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false}
1225	    = State) ->
1226    setup_data_connection(State#state{caller = {start_chunk_transfer,
1227						"STOR", RemoteFile},
1228				      client = From});
1229handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false}
1230	    = State) ->
1231    setup_data_connection(State#state{caller = {start_chunk_transfer,
1232						"APPE", RemoteFile},
1233				      client = From});
1234handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) ->
1235    send_data_message(State, Bin),
1236    {reply, ok, State};
1237
1238handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) ->
1239    {reply, {error, echunk}, State};
1240
1241handle_call({_, chunk_end}, From, #state{chunk = true} = State0) ->
1242    close_data_connection(State0),
1243    State = activate_ctrl_connection(State0),
1244    {noreply, State#state{client = From, dsock = undefined,
1245			  caller = end_chunk_transfer, chunk = false}};
1246
1247handle_call({_, chunk_end}, _, #state{chunk = false} = State) ->
1248    {reply, {error, echunk}, State};
1249
1250handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State0) ->
1251    _ = send_ctrl_message(State0, mk_cmd(Cmd, [])),
1252    State = activate_ctrl_connection(State0),
1253    {noreply, State#state{client = From, caller = quote}};
1254
1255handle_call({_, _Req}, _From, #state{csock = CSock} = State)
1256  when (CSock =:= undefined) ->
1257    {reply, {error, not_connected}, State};
1258
1259handle_call(_, _, #state{chunk = true} = State) ->
1260    {reply, {error, echunk}, State};
1261
1262%% Catch all -  This can only happen if the application programmer writes
1263%% really bad code that violates the API.
1264handle_call(Request, _Timeout, State) ->
1265    {stop, {'API_violation_connection_closed', Request},
1266     {error, {connection_terminated, 'API_violation'}}, State}.
1267
1268%%--------------------------------------------------------------------------
1269%% handle_cast(Request, State) -> {noreply, State} |
1270%%                                {noreply, State, Timeout} |
1271%%                                {stop, Reason, State}
1272%% Description: Handles cast messages.
1273%%-------------------------------------------------------------------------
1274handle_cast({Pid, close}, #state{owner = Pid} = State) ->
1275    _ = send_ctrl_message(State, mk_cmd("QUIT", [])),
1276    close_ctrl_connection(State),
1277    close_data_connection(State),
1278    {stop, normal, State#state{csock = undefined, dsock = undefined}};
1279
1280handle_cast({Pid, close}, State) ->
1281    Report = io_lib:format("A none owner process ~p tried to close an "
1282			     "ftp connection: ~n", [Pid]),
1283    error_logger:info_report(Report),
1284    {noreply, State};
1285
1286%% Catch all -  This can oly happen if the application programmer writes
1287%% really bad code that violates the API.
1288handle_cast(Msg, State) ->
1289  {stop, {'API_violation_connection_closed', Msg}, State}.
1290
1291%%--------------------------------------------------------------------------
1292%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} |
1293%%			      {stop, Reason, State}
1294%% Description: Handles tcp messages from the ftp-server.
1295%% Note: The order of the function clauses is significant.
1296%%--------------------------------------------------------------------------
1297
1298handle_info(timeout, #state{caller = open} = State) ->
1299    {stop, timeout, State};
1300
1301handle_info(timeout, State) ->
1302    {noreply, State};
1303
1304%%% Data socket messages %%%
1305handle_info({Trpt, Socket, Data},
1306	    #state{dsock = {Trpt,Socket},
1307		   caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl ->
1308    ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
1309    ok = file_write(binary_to_list(Data), Fd),
1310    progress_report({binary, Data}, State0),
1311    State = activate_data_connection(State0),
1312    {noreply, State};
1313
1314handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From,
1315					caller = recv_chunk}
1316	    = State) when Trpt==tcp;Trpt==ssl ->
1317    ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State]),
1318    gen_server:reply(From, {ok, Data}),
1319    {noreply, State#state{client = undefined, data = <<>>}};
1320
1321handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when Trpt==tcp;Trpt==ssl ->
1322    ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]),
1323    State = activate_data_connection(State0),
1324    {noreply, State#state{data = <<(State#state.data)/binary,
1325				  Data/binary>>}};
1326
1327handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
1328				  caller = {recv_file, Fd}} = State0)
1329  when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
1330    file_close(Fd),
1331    progress_report({transfer_size, 0}, State0),
1332    State = activate_ctrl_connection(State0),
1333    ?DBG("Data channel close",[]),
1334    {noreply, State#state{dsock = undefined, data = <<>>}};
1335
1336handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
1337                                  client = Client,
1338				  caller = recv_chunk} = State0)
1339  when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
1340    ?DBG("Data channel close recv_chunk",[]),
1341    State = activate_ctrl_connection(State0),
1342    {noreply, State#state{dsock = undefined,
1343                          caller = #recv_chunk_closing{dconn_closed     =  true,
1344                                                       client_called_us =  Client =/= undefined}
1345                         }};
1346
1347handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
1348					 data = Data} = State0)
1349  when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
1350    ?DBG("Data channel close",[]),
1351    State = activate_ctrl_connection(State0),
1352    {noreply, State#state{dsock = undefined, data = <<>>,
1353			  caller = {recv_bin, Data}}};
1354
1355handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data,
1356                                  caller = {handle_dir_result, Dir}}
1357	    = State0) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
1358    ?DBG("Data channel close",[]),
1359    State = activate_ctrl_connection(State0),
1360    {noreply, State#state{dsock = undefined,
1361			  caller = {handle_dir_result, Dir, Data},
1362%			  data = <<?CR,?LF>>}};
1363			  data = <<>>}};
1364
1365handle_info({Err, Socket, Reason}, #state{dsock = {Trpt,Socket},
1366					  client = From} = State)
1367  when {Err,Trpt}=={tcp_error,tcp} ; {Err,Trpt}=={ssl_error,ssl} ->
1368    gen_server:reply(From, {error, Reason}),
1369    close_data_connection(State),
1370    {noreply, State#state{dsock = undefined, client = undefined,
1371			  data = <<>>, caller = undefined, chunk = false}};
1372
1373%%% Ctrl socket messages %%%
1374handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket},
1375					      verbose = Verbose,
1376					      caller = Caller,
1377					      client = From,
1378					      ctrl_data = {BinCtrlData, AccLines,
1379							   LineStatus}}
1380	    = State0) ->
1381    ?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<<BinCtrlData/binary, Data/binary>>,State]),
1382    case ftp_response:parse_lines(<<BinCtrlData/binary, Data/binary>>,
1383				  AccLines, LineStatus) of
1384	{ok, Lines, NextMsgData} ->
1385	    verbose(Lines, Verbose, 'receive'),
1386	    CtrlResult = ftp_response:interpret(Lines),
1387	    case Caller of
1388		quote ->
1389		    gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])),
1390		    {noreply, State0#state{client = undefined,
1391                                           caller = undefined,
1392                                           latest_ctrl_response = Lines,
1393                                           ctrl_data = {NextMsgData, [],
1394                                                        start}}};
1395		_ ->
1396		    ?DBG('   ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]),
1397		    handle_ctrl_result(CtrlResult,
1398				       State0#state{latest_ctrl_response = Lines,
1399                                                    ctrl_data =
1400                                                        {NextMsgData, [], start}})
1401	    end;
1402	{continue, CtrlData} when CtrlData =/= State0#state.ctrl_data ->
1403	    ?DBG('   ...Continue... ctrl_data=~p~n',[CtrlData]),
1404	    State1 = State0#state{ctrl_data = CtrlData},
1405	    State = activate_ctrl_connection(State1),
1406	    {noreply, State};
1407	{continue, CtrlData} ->
1408	    ?DBG('   ...Continue... ctrl_data=~p~n',[CtrlData]),
1409	    {noreply, State0}
1410    end;
1411
1412%% If the server closes the control channel it is
1413%% the expected behavior that connection process terminates.
1414handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}})
1415  when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
1416    exit(normal); %% User will get error message from terminate/2
1417
1418handle_info({Err, Socket, Reason}, _) when Err==tcp_error ; Err==ssl_error ->
1419    Report =
1420	io_lib:format("~p on socket: ~p  for reason: ~p~n",
1421		      [Err, Socket, Reason]),
1422    error_logger:error_report(Report),
1423    %% If tcp does not work the only option is to terminate,
1424    %% this is the expected behavior under these circumstances.
1425    exit(normal); %% User will get error message from terminate/2
1426
1427%% Monitor messages - if the process owning the ftp connection goes
1428%% down there is no point in continuing.
1429handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) ->
1430    {stop, normal, State#state{client = undefined}};
1431
1432handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
1433    {stop, normal, State#state{client = undefined}};
1434
1435handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) ->
1436    {stop, normal, State#state{client = undefined}};
1437
1438handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
1439    {stop, {stopped, {'EXIT', Process, Reason}},
1440     State#state{client = undefined}};
1441
1442handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) ->
1443    Report = io_lib:format("Progress reporting stopped for reason ~p~n",
1444			   [Reason]),
1445    error_logger:info_report(Report),
1446    {noreply, State#state{progress = ignore}};
1447
1448%% Catch all - throws away unknown messages (This could happen by "accident"
1449%% so we do not want to crash, but we make a log entry as it is an
1450%% unwanted behaviour.)
1451handle_info(Info, State) ->
1452    Report = io_lib:format("ftp : ~p : Unexpected message: ~p~nState: ~p~n",
1453			   [self(), Info, State]),
1454    error_logger:info_report(Report),
1455    {noreply, State}.
1456
1457%%--------------------------------------------------------------------------
1458%% terminate/2 and code_change/3
1459%%--------------------------------------------------------------------------
1460terminate(normal, State) ->
1461    %% If terminate reason =/= normal the progress reporting process will
1462    %% be killed by the exit signal.
1463    progress_report(stop, State),
1464    do_terminate({error, econn}, State);
1465terminate(Reason, State) ->
1466    Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]),
1467    error_logger:error_report(Report),
1468    do_terminate({error, eclosed}, State).
1469
1470do_terminate(ErrorMsg, State) ->
1471    close_data_connection(State),
1472    close_ctrl_connection(State),
1473    case State#state.client of
1474	undefined ->
1475	    ok;
1476	From ->
1477	    gen_server:reply(From, ErrorMsg)
1478    end,
1479    ok.
1480
1481code_change(_Vsn, State1, upgrade_from_pre_5_12) ->
1482    {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
1483     Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1,
1484    IpFamily =
1485	if
1486	    (IPv6Disable =:= true) ->
1487		inet;
1488	    true ->
1489		inet6fb4
1490	end,
1491    State2 = #state{csock     = CSock,
1492		    dsock     = DSock,
1493		    verbose   = Verbose,
1494		    ldir      = LDir,
1495		    type      = Type,
1496		    chunk     = Chunk,
1497		    mode      = Mode,
1498		    timeout   = Timeout,
1499		    data      = Data,
1500		    ctrl_data = CtrlData,
1501		    owner     = Owner,
1502		    client    = Client,
1503		    caller    = Caller,
1504		    ipfamily  = IpFamily,
1505		    progress  = Progress},
1506    {ok, State2};
1507
1508code_change(_Vsn, State1, downgrade_to_pre_5_12) ->
1509    #state{csock     = CSock,
1510	   dsock     = DSock,
1511	   verbose   = Verbose,
1512	   ldir      = LDir,
1513	   type      = Type,
1514	   chunk     = Chunk,
1515	   mode      = Mode,
1516	   timeout   = Timeout,
1517	   data      = Data,
1518	   ctrl_data = CtrlData,
1519	   owner     = Owner,
1520	   client    = Client,
1521	   caller    = Caller,
1522	   ipfamily  = IpFamily,
1523	   progress  = Progress} = State1,
1524    IPv6Disable =
1525	if
1526	    (IpFamily =:= inet) ->
1527		true;
1528	    true ->
1529		false
1530	end,
1531    State2 =
1532	{state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout,
1533	 Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress},
1534    {ok, State2};
1535
1536code_change(_Vsn, State, _Extra) ->
1537    {ok, State}.
1538
1539
1540%%%=========================================================================
1541%% Start/stop
1542%%%=========================================================================
1543%%--------------------------------------------------------------------------
1544%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason}
1545%%
1546%% Description: Callback function for the ftp supervisor. It is called
1547%%            : when start_service/1 calls ftp_sup:start_child/1 to start an
1548%%            : instance of the ftp process. Also called by start_standalone/1
1549%%--------------------------------------------------------------------------
1550start_link([Opts, GenServerOptions]) ->
1551    start_link(Opts, GenServerOptions).
1552
1553start_link(Opts, GenServerOptions) ->
1554    case lists:keysearch(client, 1, Opts) of
1555	{value, _} ->
1556	    %% Via the supervisor
1557	    gen_server:start_link(?MODULE, Opts, GenServerOptions);
1558	false ->
1559	    Opts2 = [{client, self()} | Opts],
1560	    gen_server:start_link(?MODULE, Opts2, GenServerOptions)
1561    end.
1562
1563
1564%%% Stop functionality is handled by close/1
1565
1566%%%========================================================================
1567%%% Internal functions
1568%%%========================================================================
1569
1570%%--------------------------------------------------------------------------
1571%%% Help functions to handle_call and/or handle_ctrl_result
1572%%--------------------------------------------------------------------------
1573%% User handling
1574handle_user(User, Password, Acc, State0) ->
1575    _ = send_ctrl_message(State0, mk_cmd("USER ~s", [User])),
1576    State = activate_ctrl_connection(State0),
1577    {noreply, State#state{caller = {handle_user, Password, Acc}}}.
1578
1579handle_user_passwd(Password, Acc, State0) ->
1580    _ = send_ctrl_message(State0, mk_cmd("PASS ~s", [Password])),
1581    State = activate_ctrl_connection(State0),
1582    {noreply, State#state{caller = {handle_user_passwd, Acc}}}.
1583
1584handle_user_account(Acc, State0) ->
1585    _ = send_ctrl_message(State0, mk_cmd("ACCT ~s", [Acc])),
1586    State = activate_ctrl_connection(State0),
1587    {noreply, State#state{caller = handle_user_account}}.
1588
1589
1590%%--------------------------------------------------------------------------
1591%% handle_ctrl_result
1592%%--------------------------------------------------------------------------
1593handle_ctrl_result({tls_upgrade, _}, #state{csock = {tcp, Socket},
1594					    tls_options = TLSOptions,
1595					    timeout = Timeout,
1596					    caller = open, client = From}
1597		   = State0) ->
1598    ?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
1599    case ssl:connect(Socket, TLSOptions, Timeout) of
1600	{ok, TLSSocket} ->
1601	    State1 = State0#state{csock = {ssl,TLSSocket}},
1602	    _ = send_ctrl_message(State1, mk_cmd("PBSZ 0", [])),
1603	    State = activate_ctrl_connection(State1),
1604	    {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}} };
1605	{error, _} = Error ->
1606	    gen_server:reply(From,  {Error, self()}),
1607	    {stop, normal, State0#state{client = undefined,
1608					caller = undefined,
1609					tls_upgrading_data_connection = false}}
1610    end;
1611
1612handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State0) ->
1613    _ = send_ctrl_message(State0, mk_cmd("PROT P", [])),
1614    State = activate_ctrl_connection(State0),
1615    {noreply, State#state{tls_upgrading_data_connection = {true, prot}}};
1616
1617handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot},
1618					  client = From} = State) ->
1619    gen_server:reply(From,  {ok, self()}),
1620    {noreply, State#state{client = undefined,
1621			  caller = undefined,
1622			  tls_upgrading_data_connection = false}};
1623
1624handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From}
1625		   = State) ->
1626    gen_server:reply(From,  {ok, self()}),
1627    {noreply, State#state{client = undefined,
1628			  caller = undefined }};
1629handle_ctrl_result({_, Lines}, #state{caller = open} = State) ->
1630    ctrl_result_response(econn, State, {error, Lines});
1631
1632%%--------------------------------------------------------------------------
1633%% Data connection setup active mode
1634handle_ctrl_result({pos_compl, _Lines},
1635		   #state{mode   = active,
1636			  caller = {setup_data_connection,
1637				    {LSock, Caller}}} = State) ->
1638    handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}});
1639
1640handle_ctrl_result({Status, _Lines},
1641		   #state{mode   = active,
1642			  caller = {setup_data_connection, {LSock, _}}}
1643		   = State) ->
1644    close_connection({tcp,LSock}),
1645    ctrl_result_response(Status, State, {error, Status});
1646
1647%% Data connection setup passive mode
1648handle_ctrl_result({pos_compl, Lines},
1649		   #state{mode     = passive,
1650			  ipfamily = inet6,
1651			  client   = From,
1652			  caller   = {setup_data_connection, Caller},
1653			  csock    = CSock,
1654                          sockopts_data_passive = SockOpts,
1655			  timeout  = Timeout}
1656		   = State) ->
1657    [_, PortStr | _] =  lists:reverse(string:tokens(Lines, "|")),
1658    {ok, {IP, _}} = peername(CSock),
1659    case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of
1660	{ok, _, Socket} ->
1661	    handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}});
1662	{error, _Reason} = Error ->
1663	    gen_server:reply(From, Error),
1664	    {noreply, State#state{client = undefined, caller = undefined}}
1665    end;
1666
1667handle_ctrl_result({pos_compl, Lines},
1668		   #state{mode     = passive,
1669			  ipfamily = inet,
1670			  client   = From,
1671			  caller   = {setup_data_connection, Caller},
1672			  timeout  = Timeout,
1673                          sockopts_data_passive = SockOpts,
1674			  ftp_extension = false} = State) ->
1675
1676    {_, [?LEFT_PAREN | Rest]} =
1677	lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),
1678    {NewPortAddr, _} =
1679	lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),
1680    [A1, A2, A3, A4, P1, P2] =
1681	lists:map(fun(X) -> list_to_integer(X) end,
1682		  string:tokens(NewPortAddr, [$,])),
1683    IP   = {A1, A2, A3, A4},
1684    Port = (P1 * 256) + P2,
1685
1686    ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]),
1687    case connect(IP, Port, SockOpts, Timeout, State) of
1688	{ok, _, Socket}  ->
1689	    handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}});
1690	{error, _Reason} = Error ->
1691	    gen_server:reply(From, Error),
1692	    {noreply,State#state{client = undefined, caller = undefined}}
1693    end;
1694
1695handle_ctrl_result({pos_compl, Lines},
1696		   #state{mode     = passive,
1697			  ipfamily = inet,
1698			  client   = From,
1699			  caller   = {setup_data_connection, Caller},
1700			  csock    = CSock,
1701			  timeout  = Timeout,
1702                          sockopts_data_passive = SockOpts,
1703			  ftp_extension = true} = State) ->
1704
1705    [_, PortStr | _] =  lists:reverse(string:tokens(Lines, "|")),
1706    {ok, {IP, _}} = peername(CSock),
1707
1708    ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]),
1709	case connect(IP, list_to_integer(PortStr), SockOpts, Timeout, State) of
1710		{ok, _, Socket} ->
1711		    handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}});
1712		{error, _Reason} = Error ->
1713		    gen_server:reply(From, Error),
1714		    {noreply, State#state{client = undefined, caller = undefined}}
1715    end;
1716
1717
1718%% FTP server does not support passive mode: try to fallback on active mode
1719handle_ctrl_result(_,
1720		   #state{mode = passive,
1721			  caller = {setup_data_connection, Caller}} = State) ->
1722    setup_data_connection(State#state{mode = active, caller = Caller});
1723
1724
1725%%--------------------------------------------------------------------------
1726%% User handling
1727handle_ctrl_result({pos_interm, _},
1728		   #state{caller = {handle_user, PassWord, Acc}} = State) ->
1729    handle_user_passwd(PassWord, Acc, State);
1730handle_ctrl_result({Status, _},
1731		   #state{caller = {handle_user, _, _}} = State) ->
1732    ctrl_result_response(Status, State, {error, euser});
1733
1734%% Accounts
1735handle_ctrl_result({pos_interm_acct, _},
1736		   #state{caller = {handle_user_passwd, Acc}} = State)
1737  when Acc =/= "" ->
1738    handle_user_account(Acc, State);
1739handle_ctrl_result({Status, _},
1740		   #state{caller = {handle_user_passwd, _}} = State) ->
1741    ctrl_result_response(Status, State, {error, euser});
1742
1743%%--------------------------------------------------------------------------
1744%% Print current working directory
1745handle_ctrl_result({pos_compl, Lines},
1746		   #state{caller = pwd, client = From} = State) ->
1747    Dir = pwd_result(Lines),
1748    gen_server:reply(From, {ok, Dir}),
1749    {noreply, State#state{client = undefined, caller = undefined}};
1750
1751%%--------------------------------------------------------------------------
1752%% Directory listing
1753handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) ->
1754    case accept_data_connection(State0) of
1755	{ok, State1} ->
1756	    State = activate_data_connection(State1),
1757	    {noreply, State#state{caller = {handle_dir_result, Dir}}};
1758	{error, _Reason} = ERROR ->
1759	    case State0#state.client of
1760		undefined ->
1761		    {stop, ERROR, State0};
1762		From ->
1763		    gen_server:reply(From, ERROR),
1764		    {stop, normal, State0#state{client = undefined}}
1765	    end
1766    end;
1767
1768handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir,
1769						    Data}, client = From}
1770		   = State) ->
1771    case Dir of
1772	"" -> % Current directory
1773	    gen_server:reply(From, {ok, Data}),
1774	    {noreply, State#state{client = undefined,
1775				  caller = undefined}};
1776	_ ->
1777	    %% <WTF>
1778	    %% Dir cannot be assumed to be a dir. It is a string that
1779	    %% could be a dir, but could also be a file or even a string
1780	    %% containing wildcards (*).
1781	    %%
1782	    %% %% If there is only one line it might be a directory with one
1783	    %% %% file but it might be an error message that the directory
1784	    %% %% was not found. So in this case we have to endure a little
1785	    %% %% overhead to be able to give a good return value. Alas not
1786	    %% %% all ftp implementations behave the same and returning
1787	    %% %% an error string is allowed by the FTP RFC.
1788	    %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end,
1789	    %% 			 binary_to_list(Data)) of
1790	    %% 	L when (L =:= [?CR, ?LF]) orelse (L =:= []) ->
1791	    %% 	    send_ctrl_message(State, mk_cmd("PWD", [])),
1792	    %% 	    activate_ctrl_connection(State),
1793	    %% 	    {noreply,
1794	    %% 	     State#state{caller = {handle_dir_data, Dir, Data}}};
1795	    %% 	_ ->
1796	    %% 	    gen_server:reply(From, {ok, Data}),
1797	    %% 	    {noreply, State#state{client = undefined,
1798	    %% 				  caller = undefined}}
1799	    %% end
1800	    %% </WTF>
1801	    gen_server:reply(From, {ok, Data}),
1802	    {noreply, State#state{client = undefined,
1803				  caller = undefined}}
1804    end;
1805
1806handle_ctrl_result({pos_compl, Lines},
1807		   #state{caller = {handle_dir_data, Dir, DirData}} =
1808		   State0) ->
1809    OldDir = pwd_result(Lines),
1810    _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [Dir])),
1811    State = activate_ctrl_connection(State0),
1812    {noreply, State#state{caller = {handle_dir_data_second_phase, OldDir,
1813				    DirData}}};
1814handle_ctrl_result({Status, _},
1815		   #state{caller = {handle_dir_data, _, _}} = State) ->
1816    ctrl_result_response(Status, State, {error, epath});
1817
1818handle_ctrl_result(S={_Status, _},
1819		   #state{caller = {handle_dir_result, _, _}} = State) ->
1820    %% OTP-5731, macosx
1821    ctrl_result_response(S, State, {error, epath});
1822
1823handle_ctrl_result({pos_compl, _},
1824		   #state{caller = {handle_dir_data_second_phase, OldDir,
1825				    DirData}} = State0) ->
1826    _ = send_ctrl_message(State0, mk_cmd("CWD ~s", [OldDir])),
1827    State = activate_ctrl_connection(State0),
1828    {noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}};
1829handle_ctrl_result({Status, _},
1830		   #state{caller = {handle_dir_data_second_phase, _, _}}
1831		   = State) ->
1832    ctrl_result_response(Status, State, {error, epath});
1833handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData},
1834			     client = From} = State) ->
1835    gen_server:reply(From, {ok, DirData}),
1836    {noreply, State#state{client = undefined, caller = undefined}};
1837
1838handle_ctrl_result({Status, _}, #state{caller = cd} = State) ->
1839    ctrl_result_response(Status, State, {error, Status});
1840
1841handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) ->
1842     ctrl_result_response(Status, State, {error, epath});
1843
1844%%--------------------------------------------------------------------------
1845%% File renaming
1846handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}}
1847		   = State0) ->
1848    _ = send_ctrl_message(State0, mk_cmd("RNTO ~s", [NewFile])),
1849    State = activate_ctrl_connection(State0),
1850    {noreply, State#state{caller = rename_second_phase}};
1851
1852handle_ctrl_result({Status, _},
1853		   #state{caller = {rename, _}} = State) ->
1854    ctrl_result_response(Status, State, {error, Status});
1855
1856handle_ctrl_result({Status, _},
1857		   #state{caller = rename_second_phase} = State) ->
1858    ctrl_result_response(Status, State, {error, Status});
1859
1860%%--------------------------------------------------------------------------
1861%% File handling - recv_bin
1862handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) ->
1863    case accept_data_connection(State0) of
1864	{ok, State1} ->
1865	    State = activate_data_connection(State1),
1866	    {noreply, State};
1867	{error, _Reason} = ERROR ->
1868	    case State0#state.client of
1869		undefined ->
1870		    {stop, ERROR, State0};
1871		From ->
1872		    gen_server:reply(From, ERROR),
1873		    {stop, normal, State0#state{client = undefined}}
1874	    end
1875    end;
1876
1877handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data},
1878					  client = From} = State) ->
1879    gen_server:reply(From, {ok, Data}),
1880    close_data_connection(State),
1881    {noreply, State#state{client = undefined, caller = undefined}};
1882
1883handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) ->
1884    close_data_connection(State),
1885    ctrl_result_response(Status, State#state{dsock = undefined},
1886			 {error, epath});
1887
1888handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) ->
1889    close_data_connection(State),
1890    ctrl_result_response(Status, State#state{dsock = undefined},
1891			 {error, epath});
1892%%--------------------------------------------------------------------------
1893%% File handling - start_chunk_transfer
1894handle_ctrl_result({pos_prel, _}, #state{client = From,
1895					 caller = start_chunk_transfer}
1896		   = State0) ->
1897    case accept_data_connection(State0) of
1898	{ok, State1} ->
1899	    State = start_chunk(State1),
1900	    {noreply, State};
1901	{error, _Reason} = ERROR ->
1902	    case State0#state.client of
1903		undefined ->
1904		    {stop, ERROR, State0};
1905		From ->
1906		    gen_server:reply(From, ERROR),
1907		    {stop, normal, State0#state{client = undefined}}
1908	    end
1909    end;
1910
1911%%--------------------------------------------------------------------------
1912%% File handling - chunk_transfer complete
1913
1914handle_ctrl_result({pos_compl, _}, #state{client = From,
1915                                          caller = #recv_chunk_closing{dconn_closed       = true,
1916                                                                       client_called_us   = true,
1917                                                                       pos_compl_received = false
1918                                                                      }}
1919		   = State0) when From =/= undefined ->
1920    %% The pos_compl was the last event we waited for, finnish and clean up
1921    ?DBG("recv_chunk_closing pos_compl, last event",[]),
1922    gen_server:reply(From, ok),
1923    State = activate_ctrl_connection(State0),
1924    {noreply, State#state{caller = undefined,
1925                          chunk = false,
1926                          client = undefined}};
1927
1928handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R}
1929		   = State0) ->
1930    %% Waiting for more, don't care what
1931    ?DBG("recv_chunk_closing pos_compl, wait more",[]),
1932    {noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}};
1933
1934
1935%%--------------------------------------------------------------------------
1936%% File handling - recv_file
1937handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) ->
1938    case accept_data_connection(State0) of
1939	{ok, State1} ->
1940	    State = activate_data_connection(State1),
1941	    {noreply, State};
1942	{error, _Reason} = ERROR ->
1943	    case State0#state.client of
1944		undefined ->
1945		    {stop, ERROR, State0};
1946		From ->
1947		    gen_server:reply(From, ERROR),
1948		    {stop, normal, State0#state{client = undefined}}
1949	    end
1950    end;
1951
1952handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) ->
1953    file_close(Fd),
1954    close_data_connection(State),
1955    ctrl_result_response(Status, State#state{dsock = undefined},
1956			 {error, epath});
1957%%--------------------------------------------------------------------------
1958%% File handling - transfer_*
1959handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}}
1960		   = State0) ->
1961    case accept_data_connection(State0) of
1962	{ok, State1} ->
1963	    send_file(State1, Fd);
1964	{error, _Reason} = ERROR ->
1965	    case State0#state.client of
1966		undefined ->
1967		    {stop, ERROR, State0};
1968		From ->
1969		    gen_server:reply(From, ERROR),
1970		    {stop, normal, State0#state{client = undefined}}
1971	    end
1972    end;
1973
1974handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}}
1975		   = State0) ->
1976    case accept_data_connection(State0) of
1977	{ok, State} ->
1978	    send_bin(State, Bin);
1979	{error, _Reason} = ERROR ->
1980	    case State0#state.client of
1981		undefined ->
1982		    {stop, ERROR, State0};
1983		From ->
1984		    gen_server:reply(From, ERROR),
1985		    {stop, normal, State0#state{client = undefined}}
1986	    end
1987    end;
1988
1989%%--------------------------------------------------------------------------
1990%% Default
1991handle_ctrl_result({Status, _Lines}, #state{client = From} = State)
1992  when From =/= undefined ->
1993    ctrl_result_response(Status, State, {error, Status}).
1994
1995%%--------------------------------------------------------------------------
1996%% Help functions to handle_ctrl_result
1997%%--------------------------------------------------------------------------
1998ctrl_result_response(pos_compl, #state{client = From} = State, _)  ->
1999    gen_server:reply(From, ok),
2000    {noreply, State#state{client = undefined, caller = undefined}};
2001
2002ctrl_result_response(enofile, #state{client = From} = State, _) ->
2003    gen_server:reply(From, {error, enofile}),
2004    {noreply, State#state{client = undefined, caller = undefined}};
2005
2006ctrl_result_response(Status, #state{client = From} = State, _)
2007  when (Status =:= etnospc)  orelse
2008       (Status =:= epnospc)  orelse
2009       (Status =:= efnamena) orelse
2010       (Status =:= econn) ->
2011    gen_server:reply(From, {error, Status}),
2012%%    {stop, normal, {error, Status}, State#state{client = undefined}};
2013    {stop, normal, State#state{client = undefined}};
2014
2015ctrl_result_response(_, #state{client = From} = State, ErrorMsg) ->
2016    gen_server:reply(From, ErrorMsg),
2017    {noreply, State#state{client = undefined, caller = undefined}}.
2018
2019%%--------------------------------------------------------------------------
2020handle_caller(#state{caller = {dir, Dir, Len}} = State0) ->
2021    Cmd = case Len of
2022	      short -> "NLST";
2023	      long -> "LIST"
2024	  end,
2025    _ = case Dir of
2026            "" ->
2027                send_ctrl_message(State0, mk_cmd(Cmd, ""));
2028            _ ->
2029                send_ctrl_message(State0, mk_cmd(Cmd ++ " ~s", [Dir]))
2030        end,
2031    State = activate_ctrl_connection(State0),
2032    {noreply, State#state{caller = {dir, Dir}}};
2033
2034handle_caller(#state{caller = {recv_bin, RemoteFile}} = State0) ->
2035    _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
2036    State = activate_ctrl_connection(State0),
2037    {noreply, State#state{caller = recv_bin}};
2038
2039handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} =
2040	      State0) ->
2041    _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
2042    State = activate_ctrl_connection(State0),
2043    {noreply, State#state{caller = start_chunk_transfer}};
2044
2045handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State0) ->
2046    _ = send_ctrl_message(State0, mk_cmd("RETR ~s", [RemoteFile])),
2047    State = activate_ctrl_connection(State0),
2048    {noreply, State#state{caller = {recv_file, Fd}}};
2049
2050handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}},
2051		     ldir = LocalDir, client = From} = State0) ->
2052    case file_open(filename:absname(LocalFile, LocalDir), read) of
2053	{ok, Fd} ->
2054	    _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
2055	    State = activate_ctrl_connection(State0),
2056	    {noreply, State#state{caller = {transfer_file, Fd}}};
2057	{error, _} ->
2058	    gen_server:reply(From, {error, epath}),
2059	    {noreply, State0#state{client = undefined, caller = undefined,
2060                                   dsock = undefined}}
2061    end;
2062
2063handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} =
2064	      State0) ->
2065    _ = send_ctrl_message(State0, mk_cmd("~s ~s", [Cmd, RemoteFile])),
2066    State = activate_ctrl_connection(State0),
2067    {noreply, State#state{caller = {transfer_data, Bin}}}.
2068
2069%%  ----------- FTP SERVER COMMUNICATION  -------------------------
2070
2071%% Connect to FTP server at Host (default is TCP port 21)
2072%% in order to establish a control connection.
2073setup_ctrl_connection(Host, Port, Timeout, #state{sockopts_ctrl = SockOpts} = State0) ->
2074    MsTime = erlang:monotonic_time(),
2075    case connect(Host, Port, SockOpts, Timeout, State0) of
2076	{ok, IpFam, CSock} ->
2077	    State1 = State0#state{csock = {tcp, CSock}, ipfamily = IpFam},
2078	    State = activate_ctrl_connection(State1),
2079	    case Timeout - millisec_passed(MsTime) of
2080		Timeout2 when (Timeout2 >= 0) ->
2081		    {ok, State#state{caller = open}, Timeout2};
2082		_ ->
2083                    %% Oups: Simulate timeout
2084		    {ok, State#state{caller = open}, 0}
2085	    end;
2086	Error ->
2087	    Error
2088    end.
2089
2090setup_data_connection(#state{mode   = active,
2091			     caller = Caller,
2092			     csock  = CSock,
2093                             sockopts_data_active = SockOpts,
2094			     ftp_extension = FtpExt} = State0) ->
2095    case (catch sockname(CSock)) of
2096	{ok, {{_, _, _, _, _, _, _, _} = IP0, _}} ->
2097            IP = proplists:get_value(ip, SockOpts, IP0),
2098	    {ok, LSock} =
2099		gen_tcp:listen(0, [{ip, IP}, {active, false},
2100				   inet6, binary, {packet, 0} |
2101                                   lists:keydelete(ip,1,SockOpts)]),
2102	    {ok, {_, Port}} = sockname({tcp,LSock}),
2103	    IpAddress = inet_parse:ntoa(IP),
2104	    Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]),
2105	    _ = send_ctrl_message(State0, Cmd),
2106	    State = activate_ctrl_connection(State0),
2107	    {noreply, State#state{caller = {setup_data_connection,
2108					    {LSock, Caller}}}};
2109	{ok, {{_,_,_,_} = IP0, _}} ->
2110            IP = proplists:get_value(ip, SockOpts, IP0),
2111	    {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false},
2112					     binary, {packet, 0} |
2113                                             lists:keydelete(ip,1,SockOpts)]),
2114	    {ok, Port} = inet:port(LSock),
2115	    _ = case FtpExt of
2116                    false ->
2117                        {IP1, IP2, IP3, IP4} = IP,
2118                        {Port1, Port2} = {Port div 256, Port rem 256},
2119                        send_ctrl_message(State0,
2120                                          mk_cmd("PORT ~w,~w,~w,~w,~w,~w",
2121                                                 [IP1, IP2, IP3, IP4, Port1, Port2]));
2122                    true ->
2123                        IpAddress = inet_parse:ntoa(IP),
2124                        Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]),
2125                        send_ctrl_message(State0, Cmd)
2126                end,
2127	    State = activate_ctrl_connection(State0),
2128	    {noreply, State#state{caller = {setup_data_connection,
2129					    {LSock, Caller}}}}
2130    end;
2131
2132setup_data_connection(#state{mode = passive, ipfamily = inet6,
2133			     caller = Caller} = State0) ->
2134    _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
2135    State = activate_ctrl_connection(State0),
2136    {noreply, State#state{caller = {setup_data_connection, Caller}}};
2137
2138setup_data_connection(#state{mode = passive, ipfamily = inet,
2139			     caller = Caller,
2140			     ftp_extension = false} = State0) ->
2141    _ = send_ctrl_message(State0, mk_cmd("PASV", [])),
2142    State = activate_ctrl_connection(State0),
2143    {noreply, State#state{caller = {setup_data_connection, Caller}}};
2144
2145setup_data_connection(#state{mode = passive, ipfamily = inet,
2146			     caller = Caller,
2147			     ftp_extension = true} = State0) ->
2148    _ = send_ctrl_message(State0, mk_cmd("EPSV", [])),
2149    State = activate_ctrl_connection(State0),
2150    {noreply, State#state{caller = {setup_data_connection, Caller}}}.
2151
2152connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet = IpFam}) ->
2153    connect2(Host, Port, IpFam, SockOpts, Timeout);
2154
2155connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6 = IpFam}) ->
2156    connect2(Host, Port, IpFam, SockOpts, Timeout);
2157
2158connect(Host, Port, SockOpts, Timeout, #state{ipfamily = inet6fb4}) ->
2159    case inet:getaddr(Host, inet6) of
2160	{ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} ->
2161	    case inet:getaddr(Host, inet) of
2162		{ok, IPv4} ->
2163		    IpFam = inet,
2164		    connect2(IPv4, Port, IpFam, SockOpts, Timeout);
2165
2166		_ ->
2167		    IpFam = inet6,
2168		    connect2(IPv6, Port, IpFam, SockOpts, Timeout)
2169	    end;
2170
2171	{ok, IPv6} ->
2172	    IpFam = inet6,
2173	    connect2(IPv6, Port, IpFam, SockOpts, Timeout);
2174
2175	_ ->
2176	    case inet:getaddr(Host, inet) of
2177		{ok, IPv4} ->
2178		    IpFam = inet,
2179		    connect2(IPv4, Port, IpFam, SockOpts, Timeout);
2180		Error ->
2181		    Error
2182	    end
2183    end.
2184
2185connect2(Host, Port, IpFam, SockOpts, Timeout) ->
2186    Opts = [IpFam, binary, {packet, 0}, {active, false} | SockOpts],
2187    case gen_tcp:connect(Host, Port, Opts, Timeout) of
2188	{ok, Sock} ->
2189	    {ok, IpFam, Sock};
2190	Error ->
2191	    Error
2192    end.
2193
2194
2195accept_data_connection(#state{mode     = active,
2196			      dtimeout = DTimeout,
2197			      tls_options = TLSOptions,
2198			      dsock    = {lsock, LSock}} = State0) ->
2199    case gen_tcp:accept(LSock, DTimeout) of
2200	{ok, Socket} when  is_list(TLSOptions) ->
2201	    gen_tcp:close(LSock),
2202	    ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]),
2203	    case ssl:connect(Socket, TLSOptions, DTimeout) of
2204		{ok, TLSSocket} ->
2205		    {ok, State0#state{dsock={ssl,TLSSocket}}};
2206		{error, Reason} ->
2207		    {error, {ssl_connect_failed, Reason}}
2208	    end;
2209	{ok, Socket} ->
2210	    gen_tcp:close(LSock),
2211	    {ok, State0#state{dsock={tcp,Socket}}};
2212	{error, Reason} ->
2213	    {error, {data_connect_failed, Reason}}
2214    end;
2215
2216accept_data_connection(#state{mode = passive,
2217			      dtimeout = DTimeout,
2218			      dsock = {tcp,Socket},
2219			      tls_options = TLSOptions} = State) when is_list(TLSOptions) ->
2220    ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State]),
2221    case ssl:connect(Socket, TLSOptions, DTimeout) of
2222	{ok, TLSSocket} ->
2223	    {ok, State#state{dsock={ssl,TLSSocket}}};
2224	{error, Reason} ->
2225	    {error, {ssl_connect_failed, Reason}}
2226    end;
2227accept_data_connection(#state{mode = passive} = State) ->
2228    {ok,State}.
2229
2230
2231send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) ->
2232    verbose(lists:flatten(Message),Verbose,send),
2233    ?DBG('<--ctrl ~p ---- ~s~p~n',[Socket,Message,_S]),
2234    _ = send_message(Socket, Message).
2235
2236send_data_message(_S=#state{dsock = Socket}, Message) ->
2237    ?DBG('<==data ~p ==== ~s~n~p~n',[Socket,Message,_S]),
2238    case send_message(Socket, Message) of
2239	ok ->
2240	    ok;
2241	{error, Reason} ->
2242	    Report = io_lib:format("send/2 for socket ~p failed with "
2243				   "reason ~p~n", [Socket, Reason]),
2244	    error_logger:error_report(Report),
2245	    %% If tcp/ssl does not work the only option is to terminate,
2246	    %% this is the expected behavior under these circumstances.
2247	    exit(normal) %% User will get error message from terminate/2
2248    end.
2249
2250send_message({tcp, Socket}, Message) ->
2251    gen_tcp:send(Socket, Message);
2252send_message({ssl, Socket}, Message) ->
2253    ssl:send(Socket, Message).
2254
2255activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}} = State) ->
2256    activate_connection(CSock),
2257    State;
2258activate_ctrl_connection(#state{csock = CSock} = State0) ->
2259    activate_connection(CSock),
2260    %% We have already received at least part of the next control message,
2261    %% that has been saved in ctrl_data, process this first.
2262    {noreply, State} = handle_info({socket_type(CSock), unwrap_socket(CSock), <<>>}, State0),
2263    State.
2264
2265activate_data_connection(#state{dsock = DSock} = State) ->
2266    activate_connection(DSock),
2267    State.
2268
2269activate_connection(Socket) ->
2270    ignore_return_value(
2271      case socket_type(Socket) of
2272          tcp -> inet:setopts(unwrap_socket(Socket), [{active, once}]);
2273          ssl -> ssl:setopts(unwrap_socket(Socket), [{active, once}])
2274      end).
2275
2276
2277ignore_return_value(_) -> ok.
2278
2279unwrap_socket({tcp,Socket}) -> Socket;
2280unwrap_socket({ssl,Socket}) -> Socket.
2281
2282socket_type({tcp,_Socket}) -> tcp;
2283socket_type({ssl,_Socket}) -> ssl.
2284
2285close_ctrl_connection(#state{csock = undefined}) -> ok;
2286close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket).
2287
2288close_data_connection(#state{dsock = undefined}) -> ok;
2289close_data_connection(#state{dsock = Socket}) -> close_connection(Socket).
2290
2291close_connection({lsock,Socket}) -> ignore_return_value( gen_tcp:close(Socket) );
2292close_connection({tcp, Socket})  -> ignore_return_value( gen_tcp:close(Socket) );
2293close_connection({ssl, Socket})  -> ignore_return_value( ssl:close(Socket) ).
2294
2295%%  ------------ FILE HANDLING  ----------------------------------------
2296send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) ->
2297    {noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}};
2298send_file(State0, Fd) ->
2299    case file_read(Fd) of
2300	{ok, N, Bin} when N > 0 ->
2301	    send_data_message(State0, Bin),
2302	    progress_report({binary, Bin}, State0),
2303	    send_file(State0, Fd);
2304	{ok, _, _} ->
2305	    file_close(Fd),
2306	    close_data_connection(State0),
2307	    progress_report({transfer_size, 0}, State0),
2308	    State = activate_ctrl_connection(State0),
2309	    {noreply, State#state{caller = transfer_file_second_phase,
2310				  dsock = undefined}};
2311        {error, Reason} ->
2312	    gen_server:reply(State0#state.client, {error, Reason}),
2313	    {stop, normal, State0#state{client = undefined}}
2314    end.
2315
2316file_open(File, Option) ->
2317  file:open(File, [raw, binary, Option]).
2318
2319file_close(Fd) ->
2320    ignore_return_value( file:close(Fd) ).
2321
2322file_read(Fd) ->
2323    case file:read(Fd, ?FILE_BUFSIZE) of
2324	{ok, Bytes} ->
2325	    {ok, size(Bytes), Bytes};
2326	eof ->
2327	    {ok, 0, []};
2328	Other ->
2329	    Other
2330    end.
2331
2332file_write(Bytes, Fd) ->
2333    file:write(Fd, Bytes).
2334
2335%% --------------  MISC ----------------------------------------------
2336
2337call(GenServer, Msg, Format) ->
2338    call(GenServer, Msg, Format, infinity).
2339call(GenServer, Msg, Format, Timeout) ->
2340    Req = {self(), Msg},
2341    case (catch gen_server:call(GenServer, Req, Timeout)) of
2342	{ok, Bin} when is_binary(Bin) andalso (Format =:= string) ->
2343	    {ok, binary_to_list(Bin)};
2344	{'EXIT', _} ->
2345	    {error, eclosed};
2346	Result ->
2347	    Result
2348    end.
2349
2350cast(GenServer, Msg) ->
2351    gen_server:cast(GenServer, {self(), Msg}).
2352
2353send_bin(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Bin) ->
2354    State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_bin, Bin}};
2355send_bin(State0, Bin) ->
2356    send_data_message(State0, Bin),
2357    close_data_connection(State0),
2358    State = activate_ctrl_connection(State0),
2359    {noreply, State#state{caller = transfer_data_second_phase,
2360			  dsock = undefined}}.
2361
2362mk_cmd(Fmt, Args) ->
2363    [io_lib:format(Fmt, Args)| [?CR, ?LF]].		% Deep list ok.
2364
2365is_name_sane([]) ->
2366    true;
2367is_name_sane([?CR| _]) ->
2368    false;
2369is_name_sane([?LF| _]) ->
2370    false;
2371is_name_sane([_| Rest]) ->
2372    is_name_sane(Rest).
2373
2374pwd_result(Lines) ->
2375    {_, [?DOUBLE_QUOTE | Rest]} =
2376	lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines),
2377    {Dir, _} =
2378	lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest),
2379    Dir.
2380
2381
2382key_search(Key, List, Default) ->
2383    case lists:keysearch(Key, 1, List) of
2384	{value, {_,Val}} ->
2385	    Val;
2386	false ->
2387	    Default
2388    end.
2389
2390verbose(Lines, true, Direction) ->
2391    DirStr =
2392	case Direction of
2393	    send ->
2394		"Sending: ";
2395	    _ ->
2396		"Receiving: "
2397	end,
2398    Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR),
2399    erlang:display(DirStr++Str);
2400verbose(_, false,_) ->
2401    ok.
2402
2403progress(Options) ->
2404    ftp_progress:start_link(Options).
2405
2406progress_report(_, #state{progress = ignore}) ->
2407    ok;
2408progress_report(stop, #state{progress = ProgressPid}) ->
2409    ftp_progress:stop(ProgressPid);
2410progress_report({binary, Data}, #state{progress = ProgressPid}) ->
2411    ftp_progress:report(ProgressPid, {transfer_size, size(Data)});
2412progress_report(Report, #state{progress = ProgressPid}) ->
2413    ftp_progress:report(ProgressPid, Report).
2414
2415
2416peername({tcp, Socket}) -> inet:peername(Socket);
2417peername({ssl, Socket}) -> ssl:peername(Socket).
2418
2419sockname({tcp, Socket}) -> inet:sockname(Socket);
2420sockname({ssl, Socket}) -> ssl:sockname(Socket).
2421
2422maybe_tls_upgrade(Pid, undefined) ->
2423    {ok, Pid};
2424maybe_tls_upgrade(Pid, TLSOptions) ->
2425    catch ssl:start(),
2426    call(Pid, {open, tls_upgrade, TLSOptions}, plain).
2427
2428start_chunk(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State) ->
2429    State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, start_chunk, undefined}};
2430start_chunk(#state{client = From} = State) ->
2431    gen_server:reply(From, ok),
2432    State#state{chunk = true,
2433		client = undefined,
2434		caller = undefined}.
2435
2436
2437%% This function extracts the start options from the
2438%% Valid options:
2439%%     debug,
2440%%     verbose
2441%%     ipfamily
2442%%     priority
2443%%     flags    (for backward compatibillity)
2444start_options(Options) ->
2445    case lists:keysearch(flags, 1, Options) of
2446	{value, {flags, Flags}} ->
2447	    Verbose = lists:member(verbose, Flags),
2448	    IsTrace = lists:member(trace, Flags),
2449	    IsDebug = lists:member(debug, Flags),
2450	    DebugLevel =
2451		if
2452		    (IsTrace =:= true) ->
2453			trace;
2454		    IsDebug =:= true ->
2455			debug;
2456		    true ->
2457			disable
2458		end,
2459	    {ok, [{verbose,  Verbose},
2460		  {debug,    DebugLevel},
2461		  {priority, low}]};
2462	false ->
2463	    ValidateVerbose =
2464		fun(true) -> true;
2465		   (false) -> true;
2466		   (_) -> false
2467		end,
2468	    ValidateDebug =
2469		fun(trace) -> true;
2470		   (debug) -> true;
2471		   (disable) -> true;
2472		   (_) -> false
2473		end,
2474	    ValidatePriority =
2475		fun(low) -> true;
2476		   (normal) -> true;
2477		   (high) -> true;
2478		   (_) -> false
2479		end,
2480	    ValidOptions =
2481		[{verbose,  ValidateVerbose,  false, false},
2482		 {debug,    ValidateDebug,    false, disable},
2483		 {priority, ValidatePriority, false, low}],
2484	    validate_options(Options, ValidOptions, [])
2485    end.
2486
2487
2488%% This function extracts and validates the open options from the
2489%% Valid options:
2490%%    mode
2491%%    host
2492%%    port
2493%%    timeout
2494%%    dtimeout
2495%%    progress
2496%%	  ftp_extension
2497
2498open_options(Options) ->
2499    ValidateMode =
2500	fun(active) -> true;
2501	   (passive) -> true;
2502	   (_) -> false
2503	end,
2504    ValidateHost =
2505	fun(Host) when is_list(Host) ->
2506		true;
2507	   (Host) when is_tuple(Host) andalso
2508		       ((size(Host) =:= 4) orelse (size(Host) =:= 8)) ->
2509		true;
2510	   (_) ->
2511		false
2512	end,
2513    ValidatePort =
2514	fun(Port) when is_integer(Port) andalso (Port > 0) -> true;
2515	   (_) -> false
2516	end,
2517    ValidateIpFamily =
2518	fun(inet) -> true;
2519	   (inet6) -> true;
2520	   (inet6fb4) -> true;
2521	   (_) -> false
2522	end,
2523    ValidateTimeout =
2524	fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true;
2525	   (_) -> false
2526	end,
2527    ValidateDTimeout =
2528	fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true;
2529	   (infinity) -> true;
2530	   (_) -> false
2531	end,
2532    ValidateProgress =
2533	fun(ignore) ->
2534		true;
2535	   ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso
2536					     is_atom(Func) ->
2537		true;
2538	   (_) ->
2539		false
2540	end,
2541	ValidateFtpExtension =
2542	fun(true) -> true;
2543		(false) -> true;
2544		(_) -> false
2545	end,
2546    ValidOptions =
2547	[{mode,     ValidateMode,     false, ?DEFAULT_MODE},
2548	 {host,     ValidateHost,     true,  ehost},
2549	 {port,     ValidatePort,     false, ?FTP_PORT},
2550	 {ipfamily, ValidateIpFamily, false, inet},
2551	 {timeout,  ValidateTimeout,  false, ?CONNECTION_TIMEOUT},
2552	 {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT},
2553	 {progress, ValidateProgress, false, ?PROGRESS_DEFAULT},
2554	 {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}],
2555    validate_options(Options, ValidOptions, []).
2556
2557socket_options(Options) ->
2558    CtrlOpts = proplists:get_value(sock_ctrl, Options, []),
2559    DataActOpts = proplists:get_value(sock_data_act, Options, CtrlOpts),
2560    DataPassOpts = proplists:get_value(sock_data_pass, Options, CtrlOpts),
2561    case [O || O <- lists:usort(CtrlOpts++DataPassOpts++DataActOpts),
2562               not valid_socket_option(O)] of
2563        [] ->
2564            {ok, {CtrlOpts, DataPassOpts, DataActOpts}};
2565        Invalid ->
2566            throw({error,{sock_opts,Invalid}})
2567    end.
2568
2569
2570valid_socket_option(inet            ) -> false;
2571valid_socket_option(inet6           ) -> false;
2572valid_socket_option({ipv6_v6only, _}) -> false;
2573valid_socket_option({active,_}      ) -> false;
2574valid_socket_option({packet,_}      ) -> false;
2575valid_socket_option({mode,_}        ) -> false;
2576valid_socket_option(binary          ) -> false;
2577valid_socket_option(list            ) -> false;
2578valid_socket_option({header,_}      ) -> false;
2579valid_socket_option({packet_size,_} ) -> false;
2580valid_socket_option(_) -> true.
2581
2582
2583tls_options(Options) ->
2584    %% Options will be validated by ssl application
2585    proplists:get_value(tls, Options, undefined).
2586
2587validate_options([], [], Acc) ->
2588    {ok, lists:reverse(Acc)};
2589validate_options([], ValidOptions, Acc) ->
2590    %% Check if any mandatory options are missing!
2591    case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of
2592	[] ->
2593	    Defaults =
2594		[{Key, Default} || {Key, _, _, Default} <- ValidOptions],
2595	    {ok, lists:reverse(Defaults ++ Acc)};
2596	[{_, Reason}|_Missing] ->
2597	    throw({error, Reason})
2598    end;
2599validate_options([{Key, Value}|Options], ValidOptions, Acc) ->
2600    case lists:keysearch(Key, 1, ValidOptions) of
2601	{value, {Key, Validate, _, Default}} ->
2602	    case (catch Validate(Value)) of
2603		true ->
2604		    NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
2605		    validate_options(Options, NewValidOptions,
2606				     [{Key, Value} | Acc]);
2607		_ ->
2608		    NewValidOptions = lists:keydelete(Key, 1, ValidOptions),
2609		    validate_options(Options, NewValidOptions,
2610				     [{Key, Default} | Acc])
2611	    end;
2612	false ->
2613	    validate_options(Options, ValidOptions, Acc)
2614    end;
2615validate_options([_|Options], ValidOptions, Acc) ->
2616    validate_options(Options, ValidOptions, Acc).
2617
2618%% Help function, elapsed milliseconds since T0
2619millisec_passed(T0) ->
2620    %% OTP 18
2621    erlang:convert_time_unit(erlang:monotonic_time() - T0,
2622			     native,
2623			     micro_seconds) div 1000.
2624