From e0ee349fc426007c7b269660244aeda94ddadd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= Date: Mon, 26 Feb 2018 16:35:58 +0100 Subject: inets,ftp: Break out FTP from inets - Created initial directory structure for the FTP application. - Updated inets Makefiles to not include FTP related modules. - Remove ftp code from inets - Implement backward compatibility layer for FTP - Add inets_ftp_wrapper - Fix failing TCs Change-Id: I120ec5bdef0c3df4cee2d7880db2aec581505bc4 --- lib/Makefile | 2 +- lib/ftp/Makefile | 78 + lib/ftp/doc/archive/rfc2428.txt | 451 +++ lib/ftp/doc/archive/rfc2577.txt | 451 +++ lib/ftp/doc/archive/rfc959.txt | 3933 ++++++++++++++++++++ lib/ftp/doc/html/.gitignore | 0 lib/ftp/doc/man3/.gitignore | 0 lib/ftp/doc/man6/.gitignore | 0 lib/ftp/doc/pdf/.gitignore | 0 lib/ftp/doc/src/Makefile | 154 + lib/ftp/doc/src/book.xml | 49 + lib/ftp/doc/src/ftp.xml | 948 +++++ lib/ftp/doc/src/ftp_client.xml | 84 + lib/ftp/doc/src/introduction.xml | 46 + lib/ftp/doc/src/notes.xml | 53 + lib/ftp/doc/src/part.xml | 37 + lib/ftp/doc/src/ref_man.xml | 36 + lib/ftp/ebin/.gitignore | 0 lib/ftp/info | 2 + lib/ftp/src/Makefile | 118 + lib/ftp/src/ftp.app.src | 16 + lib/ftp/src/ftp.erl | 2593 +++++++++++++ lib/ftp/src/ftp_app.erl | 47 + lib/ftp/src/ftp_internal.hrl | 59 + lib/ftp/src/ftp_progress.erl | 136 + lib/ftp/src/ftp_response.erl | 203 + lib/ftp/src/ftp_sup.erl | 68 + lib/ftp/test/Makefile | 252 ++ lib/ftp/test/erl_make_certs.erl | 475 +++ lib/ftp/test/ftp.config | 1 + lib/ftp/test/ftp.cover | 2 + lib/ftp/test/ftp.spec | 1 + lib/ftp/test/ftp_SUITE.erl | 1180 ++++++ lib/ftp/test/ftp_SUITE_data/ftpd_hosts.skel | 18 + lib/ftp/test/ftp_SUITE_data/vsftpd.conf | 26 + lib/ftp/test/ftp_bench.spec | 1 + lib/ftp/test/ftp_format_SUITE.erl | 328 ++ lib/ftp/test/ftp_internal.hrl | 1 + lib/ftp/test/ftp_property_test_SUITE.erl | 53 + lib/ftp/test/inets_test_lib.erl | 596 +++ lib/ftp/test/inets_test_lib.hrl | 28 + lib/ftp/test/property_test/README | 12 + .../property_test/ftp_simple_client_server.erl | 307 ++ .../ftp_simple_client_server_data/vsftpd.conf | 26 + lib/ftp/vsn.mk | 24 + lib/inets/doc/archive/rfc2428.txt | 451 --- lib/inets/doc/archive/rfc2577.txt | 451 --- lib/inets/doc/archive/rfc959.txt | 3933 -------------------- lib/inets/doc/src/Makefile | 2 - lib/inets/doc/src/ftp.xml | 948 ----- lib/inets/doc/src/ftp_client.xml | 86 - lib/inets/doc/src/introduction.xml | 9 +- lib/inets/doc/src/part.xml | 8 +- lib/inets/doc/src/ref_man.xml | 9 +- lib/inets/src/ftp/Makefile | 104 - lib/inets/src/ftp/ftp.erl | 2596 ------------- lib/inets/src/ftp/ftp_internal.hrl | 33 - lib/inets/src/ftp/ftp_progress.erl | 136 - lib/inets/src/ftp/ftp_response.erl | 203 - lib/inets/src/ftp/ftp_sup.erl | 60 - lib/inets/src/inets_app/Makefile | 3 +- lib/inets/src/inets_app/inets.app.src | 5 +- lib/inets/src/inets_app/inets.erl | 4 +- lib/inets/src/inets_app/inets_ftp_wrapper.erl | 47 + lib/inets/src/inets_app/inets_sup.erl | 12 +- lib/inets/src/subdirs.mk | 2 +- lib/inets/test/Makefile | 40 +- lib/inets/test/ftp_SUITE.erl | 1180 ------ lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel | 18 - lib/inets/test/ftp_SUITE_data/vsftpd.conf | 26 - lib/inets/test/ftp_format_SUITE.erl | 328 -- lib/inets/test/ftp_internal.hrl | 1 - lib/inets/test/ftp_property_test_SUITE.erl | 53 - lib/inets/test/inets_SUITE.erl | 39 - lib/inets/test/inets_sup_SUITE.erl | 32 +- 75 files changed, 12964 insertions(+), 10750 deletions(-) create mode 100644 lib/ftp/Makefile create mode 100644 lib/ftp/doc/archive/rfc2428.txt create mode 100644 lib/ftp/doc/archive/rfc2577.txt create mode 100644 lib/ftp/doc/archive/rfc959.txt create mode 100644 lib/ftp/doc/html/.gitignore create mode 100644 lib/ftp/doc/man3/.gitignore create mode 100644 lib/ftp/doc/man6/.gitignore create mode 100644 lib/ftp/doc/pdf/.gitignore create mode 100644 lib/ftp/doc/src/Makefile create mode 100644 lib/ftp/doc/src/book.xml create mode 100644 lib/ftp/doc/src/ftp.xml create mode 100644 lib/ftp/doc/src/ftp_client.xml create mode 100644 lib/ftp/doc/src/introduction.xml create mode 100644 lib/ftp/doc/src/notes.xml create mode 100644 lib/ftp/doc/src/part.xml create mode 100644 lib/ftp/doc/src/ref_man.xml create mode 100644 lib/ftp/ebin/.gitignore create mode 100644 lib/ftp/info create mode 100644 lib/ftp/src/Makefile create mode 100644 lib/ftp/src/ftp.app.src create mode 100644 lib/ftp/src/ftp.erl create mode 100644 lib/ftp/src/ftp_app.erl create mode 100644 lib/ftp/src/ftp_internal.hrl create mode 100644 lib/ftp/src/ftp_progress.erl create mode 100644 lib/ftp/src/ftp_response.erl create mode 100644 lib/ftp/src/ftp_sup.erl create mode 100644 lib/ftp/test/Makefile create mode 100644 lib/ftp/test/erl_make_certs.erl create mode 100644 lib/ftp/test/ftp.config create mode 100644 lib/ftp/test/ftp.cover create mode 100644 lib/ftp/test/ftp.spec create mode 100644 lib/ftp/test/ftp_SUITE.erl create mode 100644 lib/ftp/test/ftp_SUITE_data/ftpd_hosts.skel create mode 100644 lib/ftp/test/ftp_SUITE_data/vsftpd.conf create mode 100644 lib/ftp/test/ftp_bench.spec create mode 100644 lib/ftp/test/ftp_format_SUITE.erl create mode 120000 lib/ftp/test/ftp_internal.hrl create mode 100644 lib/ftp/test/ftp_property_test_SUITE.erl create mode 100644 lib/ftp/test/inets_test_lib.erl create mode 100644 lib/ftp/test/inets_test_lib.hrl create mode 100644 lib/ftp/test/property_test/README create mode 100644 lib/ftp/test/property_test/ftp_simple_client_server.erl create mode 100644 lib/ftp/test/property_test/ftp_simple_client_server_data/vsftpd.conf create mode 100644 lib/ftp/vsn.mk delete mode 100644 lib/inets/doc/archive/rfc2428.txt delete mode 100644 lib/inets/doc/archive/rfc2577.txt delete mode 100644 lib/inets/doc/archive/rfc959.txt delete mode 100644 lib/inets/doc/src/ftp.xml delete mode 100644 lib/inets/doc/src/ftp_client.xml delete mode 100644 lib/inets/src/ftp/Makefile delete mode 100644 lib/inets/src/ftp/ftp.erl delete mode 100644 lib/inets/src/ftp/ftp_internal.hrl delete mode 100644 lib/inets/src/ftp/ftp_progress.erl delete mode 100644 lib/inets/src/ftp/ftp_response.erl delete mode 100644 lib/inets/src/ftp/ftp_sup.erl create mode 100644 lib/inets/src/inets_app/inets_ftp_wrapper.erl delete mode 100644 lib/inets/test/ftp_SUITE.erl delete mode 100644 lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel delete mode 100644 lib/inets/test/ftp_SUITE_data/vsftpd.conf delete mode 100644 lib/inets/test/ftp_format_SUITE.erl delete mode 120000 lib/inets/test/ftp_internal.hrl delete mode 100644 lib/inets/test/ftp_property_test_SUITE.erl diff --git a/lib/Makefile b/lib/Makefile index ae466ed518..d04af322eb 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -35,7 +35,7 @@ ALL_ERLANG_APPLICATIONS = xmerl edoc erl_docgen snmp otp_mibs erl_interface \ public_key ssl observer odbc diameter \ cosTransactions cosEvent cosTime cosNotification \ cosProperty cosFileTransfer cosEventDomain et megaco \ - eunit ssh eldap dialyzer hipe + eunit ssh eldap dialyzer hipe ftp ifdef BUILD_ALL ERLANG_APPLICATIONS += $(ALL_ERLANG_APPLICATIONS) diff --git a/lib/ftp/Makefile b/lib/ftp/Makefile new file mode 100644 index 0000000000..555f8b0dea --- /dev/null +++ b/lib/ftp/Makefile @@ -0,0 +1,78 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-2016. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% +# +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Macros +# ---------------------------------------------------- + +SUB_DIRECTORIES = src doc/src + +include vsn.mk +VSN = $(FTP_VSN) + +SPECIAL_TARGETS = + +DIA_PLT = ./priv/plt/$(APPLICATION).plt +DIA_ANALYSIS = $(basename $(DIA_PLT)).dialyzer_analysis + + +# ---------------------------------------------------- +# Default Subdir Targets +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_subdir.mk + +.PHONY: info gclean dialyzer dialyzer_plt dclean + +info: + @echo "OS: $(OS)" + @echo "DOCB: $(DOCB)" + @echo "" + @echo "FTP_VSN: $(FTP_VSN)" + @echo "APP_VSN: $(APP_VSN)" + @echo "" + @echo "DIA_PLT: $(DIA_PLT)" + @echo "DIA_ANALYSIS: $(DIA_ANALYSIS)" + @echo "" + +gclean: + git clean -fXd + +dclean: + rm -f $(DIA_PLT) + rm -f $(DIA_ANALYSIS) + +dialyzer_plt: $(DIA_PLT) + +$(DIA_PLT): + @echo "Building $(APPLICATION) plt file" + @dialyzer --build_plt \ + --output_plt $@ \ + -r ../$(APPLICATION)/ebin \ + --output $(DIA_ANALYSIS) \ + --verbose + +dialyzer: $(DIA_PLT) + @echo "Running dialyzer on $(APPLICATION)" + @dialyzer --plt $< \ + ../$(APPLICATION)/ebin \ + --verbose diff --git a/lib/ftp/doc/archive/rfc2428.txt b/lib/ftp/doc/archive/rfc2428.txt new file mode 100644 index 0000000000..a6ec3535ed --- /dev/null +++ b/lib/ftp/doc/archive/rfc2428.txt @@ -0,0 +1,451 @@ + + + + + + +Network Working Group M. Allman +Request for Comments: 2428 NASA Lewis/Sterling Software +Category: Standards Track S. Ostermann + Ohio University + C. Metz + The Inner Net + September 1998 + + + FTP Extensions for IPv6 and NATs + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + The specification for the File Transfer Protocol assumes that the + underlying network protocol uses a 32-bit network address + (specifically IP version 4). With the deployment of version 6 of the + Internet Protocol, network addresses will no longer be 32-bits. This + paper specifies extensions to FTP that will allow the protocol to + work over IPv4 and IPv6. In addition, the framework defined can + support additional network protocols in the future. + +1. Introduction + + The keywords, such as MUST and SHOULD, found in this document are + used as defined in RFC 2119 [Bra97]. + + The File Transfer Protocol [PR85] only provides the ability to + communicate information about IPv4 data connections. FTP assumes + network addresses will be 32 bits in length. However, with the + deployment of version 6 of the Internet Protocol [DH96] addresses + will no longer be 32 bits long. RFC 1639 [Pis94] specifies + extensions to FTP to enable its use over various network protocols. + Unfortunately, the mechanism can fail in a multi-protocol + environment. During the transition between IPv4 and IPv6, FTP needs + the ability to negotiate the network protocol that will be used for + data transfer. + + + +Allman, et. al. Standards Track [Page 1] + +RFC 2428 FTP Extensions for IPv6 and NATs September 1998 + + + This document provides a specification for a way that FTP can + communicate data connection endpoint information for network + protocols other than IPv4. In this specification, the FTP commands + PORT and PASV are replaced with EPRT and EPSV, respectively. This + document is organized as follows. Section 2 outlines the EPRT + command and Section 3 outlines the EPSV command. Section 4 defines + the utilization of these two new FTP commands. Section 5 briefly + presents security considerations. Finally, Section 6 provides + conclusions. + +2. The EPRT Command + + The EPRT command allows for the specification of an extended address + for the data connection. The extended address MUST consist of the + network protocol as well as the network and transport addresses. The + format of EPRT is: + + EPRT + + The EPRT command keyword MUST be followed by a single space (ASCII + 32). Following the space, a delimiter character () MUST be + specified. The delimiter character MUST be one of the ASCII + characters in range 33-126 inclusive. The character "|" (ASCII 124) + is recommended unless it coincides with a character needed to encode + the network address. + + The argument MUST be an address family number defined by + IANA in the latest Assigned Numbers RFC (RFC 1700 [RP94] as of the + writing of this document). This number indicates the protocol to be + used (and, implicitly, the address length). This document will use + two of address family numbers from [RP94] as examples, according to + the following table: + + AF Number Protocol + --------- -------- + 1 Internet Protocol, Version 4 [Pos81a] + 2 Internet Protocol, Version 6 [DH96] + + The is a protocol specific string representation of the + network address. For the two address families specified above (AF + Number 1 and 2), addresses MUST be in the following format: + + AF Number Address Format Example + --------- -------------- ------- + 1 dotted decimal 132.235.1.2 + 2 IPv6 string 1080::8:800:200C:417A + representations + defined in [HD96] + + + +Allman, et. al. Standards Track [Page 2] + +RFC 2428 FTP Extensions for IPv6 and NATs September 1998 + + + The argument must be the string representation of the + number of the TCP port on which the host is listening for the data + connection. + + The following are sample EPRT commands: + + EPRT |1|132.235.1.2|6275| + + EPRT |2|1080::8:800:200C:417A|5282| + + The first command specifies that the server should use IPv4 to open a + data connection to the host "132.235.1.2" on TCP port 6275. The + second command specifies that the server should use the IPv6 network + protocol and the network address "1080::8:800:200C:417A" to open a + TCP data connection on port 5282. + + Upon receipt of a valid EPRT command, the server MUST return a code + of 200 (Command OK). The standard negative error code 500 and 501 + [PR85] are sufficient to handle most errors (e.g., syntax errors) + involving the EPRT command. However, an additional error code is + needed. The response code 522 indicates that the server does not + support the requested network protocol. The interpretation of this + new error code is: + + 5yz Negative Completion + x2z Connections + xy2 Extended Port Failure - unknown network protocol + + The text portion of the response MUST indicate which network + protocols the server does support. If the network protocol is + unsupported, the format of the response string MUST be: + + \ + (prot1,prot2,...,protn) + + Both the numeric code specified above and the protocol information + between the characters '(' and ')' are intended for the software + automata receiving the response; the textual message between the + numeric code and the '(' is intended for the human user and can be + any arbitrary text, but MUST NOT include the characters '(' and ')'. + In the above case, the text SHOULD indicate that the network protocol + in the EPRT command is not supported by the server. The list of + protocols inside the parenthesis MUST be a comma separated list of + address family numbers. Two example response strings follow: + + Network protocol not supported, use (1) + + Network protocol not supported, use (1,2) + + + +Allman, et. al. Standards Track [Page 3] + +RFC 2428 FTP Extensions for IPv6 and NATs September 1998 + + +3. The EPSV Command + + The EPSV command requests that a server listen on a data port and + wait for a connection. The EPSV command takes an optional argument. + The response to this command includes only the TCP port number of the + listening connection. The format of the response, however, is + similar to the argument of the EPRT command. This allows the same + parsing routines to be used for both commands. In addition, the + format leaves a place holder for the network protocol and/or network + address, which may be needed in the EPSV response in the future. The + response code for entering passive mode using an extended address + MUST be 229. The interpretation of this code, according to [PR85] + is: + + 2yz Positive Completion + x2z Connections + xy9 Extended Passive Mode Entered + + The text returned in response to the EPSV command MUST be: + + \ + () + + The portion of the string enclosed in parentheses MUST be the exact + string needed by the EPRT command to open the data connection, as + specified above. + + The first two fields contained in the parenthesis MUST be blank. The + third field MUST be the string representation of the TCP port number + on which the server is listening for a data connection. The network + protocol used by the data connection will be the same network + protocol used by the control connection. In addition, the network + address used to establish the data connection will be the same + network address used for the control connection. An example response + string follows: + + Entering Extended Passive Mode (|||6446|) + + The standard negative error codes 500 and 501 are sufficient to + handle all errors involving the EPSV command (e.g., syntax errors). + + When the EPSV command is issued with no argument, the server will + choose the network protocol for the data connection based on the + protocol used for the control connection. However, in the case of + proxy FTP, this protocol might not be appropriate for communication + between the two servers. Therefore, the client needs to be able to + request a specific protocol. If the server returns a protocol that + is not supported by the host that will be connecting to the port, the + + + +Allman, et. al. Standards Track [Page 4] + +RFC 2428 FTP Extensions for IPv6 and NATs September 1998 + + + client MUST issue an ABOR (abort) command to allow the server to + close down the listening connection. The client can then send an + EPSV command requesting the use of a specific network protocol, as + follows: + + EPSV + + If the requested protocol is supported by the server, it SHOULD use + the protocol. If not, the server MUST return the 522 error messages + as outlined in section 2. + + Finally, the EPSV command can be used with the argument "ALL" to + inform Network Address Translators that the EPRT command (as well as + other data commands) will no longer be used. An example of this + command follows: + + EPSVALL + + Upon receipt of an EPSV ALL command, the server MUST reject all data + connection setup commands other than EPSV (i.e., EPRT, PORT, PASV, et + al.). This use of the EPSV command is further explained in section + 4. + +4. Command Usage + + For all FTP transfers where the control and data connection(s) are + being established between the same two machines, the EPSV command + MUST be used. Using the EPSV command benefits performance of + transfers that traverse firewalls or Network Address Translators + (NATs). RFC 1579 [Bel94] recommends using the passive command when + behind firewalls since firewalls do not generally allow incoming + connections (which are required when using the PORT (EPRT) command). + In addition, using EPSV as defined in this document does not require + NATs to change the network address in the traffic as it is forwarded. + The NAT would have to change the address if the EPRT command was + used. Finally, if the client issues an "EPSV ALL" command, NATs may + be able to put the connection on a "fast path" through the + translator, as the EPRT command will never be used and therefore, + translation of the data portion of the segments will never be needed. + When a client only expects to do two-way FTP transfers, it SHOULD + issue this command as soon as possible. If a client later finds that + it must do a three-way FTP transfer after issuing an EPSV ALL + command, a new FTP session MUST be started. + + + + + + + + +Allman, et. al. Standards Track [Page 5] + +RFC 2428 FTP Extensions for IPv6 and NATs September 1998 + + +5. Security Issues + + The authors do not believe that these changes to FTP introduce new + security problems. A companion Work in Progress [AO98] is a more + general discussion of FTP security issues and techniques to reduce + these security problems. + +6. Conclusions + + The extensions specified in this paper will enable FTP to operate + over a variety of network protocols. + +References + + [AO98] Allman, M., and S. Ostermann, "FTP Security + Considerations", Work in Progress. + + [Bel94] Bellovin, S., "Firewall-Friendly FTP", RFC 1579, February + 1994. + + [Bra97] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [DH96] Deering, S., and R. Hinden, "Internet Protocol, Version 6 + (IPv6) Specification", RFC 1883, December 1995. + + [HD96] Hinden, R., and S. Deering, "IP Version 6 Addressing + Architecture", RFC 2373, July 1998. + + [Pis94] Piscitello, D., "FTP Operation Over Big Address Records + (FOOBAR)", RFC 1639, June 1994. + + [Pos81a] Postel, J., "Internet Protocol", STD 5, RFC 791, September + 1981. + + [Pos81b] Postel, J., "Transmission Control Protocol", STD 7, RFC 793, + September 1981. + + [PR85] Postel, J., and J. Reynolds, "File Transfer Protocol (FTP)", + STD 9, RFC 959, October 1985. + + [RP94] Reynolds, J., and J. Postel, "Assigned Numbers", STD 2, RFC + 1700, October 1994. See also: + http://www.iana.org/numbers.html + + + + + + + +Allman, et. al. Standards Track [Page 6] + +RFC 2428 FTP Extensions for IPv6 and NATs September 1998 + + +Authors' Addresses + + Mark Allman + NASA Lewis Research Center/Sterling Software + 21000 Brookpark Rd. MS 54-2 + Cleveland, OH 44135 + + Phone: (216) 433-6586 + EMail: mallman@lerc.nasa.gov + http://gigahertz.lerc.nasa.gov/~mallman/ + + + Shawn Ostermann + School of Electrical Engineering and Computer Science + Ohio University + 416 Morton Hall + Athens, OH 45701 + + Phone: (740) 593-1234 + EMail: ostermann@cs.ohiou.edu + + + Craig Metz + The Inner Net + Box 10314-1954 + Blacksburg, VA 24062-0314 + + Phone: (DSN) 754-8590 + EMail: cmetz@inner.net + + + + + + + + + + + + + + + + + + + + + + +Allman, et. al. Standards Track [Page 7] + +RFC 2428 FTP Extensions for IPv6 and NATs September 1998 + + +Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Allman, et. al. Standards Track [Page 8] + diff --git a/lib/ftp/doc/archive/rfc2577.txt b/lib/ftp/doc/archive/rfc2577.txt new file mode 100644 index 0000000000..83ba203130 --- /dev/null +++ b/lib/ftp/doc/archive/rfc2577.txt @@ -0,0 +1,451 @@ + + + + + + +Network Working Group M. Allman +Request for Comments: 2577 NASA Glenn/Sterling Software +Category: Informational S. Ostermann + Ohio University + May 1999 + + + FTP Security Considerations + +Status of this Memo + + This memo provides information for the Internet community. It does + not specify an Internet standard of any kind. Distribution of this + memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1999). All Rights Reserved. + +Abstract + + The specification for the File Transfer Protocol (FTP) contains a + number of mechanisms that can be used to compromise network security. + The FTP specification allows a client to instruct a server to + transfer files to a third machine. This third-party mechanism, known + as proxy FTP, causes a well known security problem. The FTP + specification also allows an unlimited number of attempts at entering + a user's password. This allows brute force "password guessing" + attacks. This document provides suggestions for system + administrators and those implementing FTP servers that will decrease + the security problems associated with FTP. + +1 Introduction + + The File Transfer Protocol specification (FTP) [PR85] provides a + mechanism that allows a client to establish an FTP control connection + and transfer a file between two FTP servers. This "proxy FTP" + mechanism can be used to decrease the amount of traffic on the + network; the client instructs one server to transfer a file to + another server, rather than transferring the file from the first + server to the client and then from the client to the second server. + This is particularly useful when the client connects to the network + using a slow link (e.g., a modem). While useful, proxy FTP provides + a security problem known as a "bounce attack" [CERT97:27]. In + addition to the bounce attack, FTP servers can be used by attackers + to guess passwords using brute force. + + + + + +Allman & Ostermann Informational [Page 1] + +RFC 2577 FTP Security Considerations May 1999 + + + This document does not contain a discussion of FTP when used in + conjunction with strong security protocols, such as IP Security. + These security concerns should be documented, however they are out of + the scope of this document. + + This paper provides information for FTP server implementers and + system administrators, as follows. Section 2 describes the FTP + "bounce attack". Section 3 provides suggestions for minimizing the + bounce attack. Section 4 provides suggestions for servers which + limit access based on network address. Section 5 provides + recommendations for limiting brute force "password guessing" by + clients. Next, section 6 provides a brief discussion of mechanisms + to improve privacy. Section 7 provides a mechanism to prevent user + identity guessing. Section 8 discusses the practice of port + stealing. Finally, section 9 provides an overview of other FTP + security issues related to software bugs rather than protocol issues. + +2 The Bounce Attack + + The version of FTP specified in the standard [PR85] provides a method + for attacking well known network servers, while making the + perpetrators difficult to track down. The attack involves sending an + FTP "PORT" command to an FTP server containing the network address + and the port number of the machine and service being attacked. At + this point, the original client can instruct the FTP server to send a + file to the service being attacked. Such a file would contain + commands relevant to the service being attacked (SMTP, NNTP, etc.). + Instructing a third party to connect to the service, rather than + connecting directly, makes tracking down the perpetrator difficult + and can circumvent network-address-based access restrictions. + + As an example, a client uploads a file containing SMTP commands to an + FTP server. Then, using an appropriate PORT command, the client + instructs the server to open a connection to a third machine's SMTP + port. Finally, the client instructs the server to transfer the + uploaded file containing SMTP commands to the third machine. This + may allow the client to forge mail on the third machine without + making a direct connection. This makes it difficult to track + attackers. + +3 Protecting Against the Bounce Attack + + The original FTP specification [PR85] assumes that data connections + will be made using the Transmission Control Protocol (TCP) [Pos81]. + TCP port numbers in the range 0 - 1023 are reserved for well known + services such as mail, network news and FTP control connections + [RP94]. The FTP specification makes no restrictions on the TCP port + number used for the data connection. Therefore, using proxy FTP, + + + +Allman & Ostermann Informational [Page 2] + +RFC 2577 FTP Security Considerations May 1999 + + + clients have the ability to tell the server to attack a well known + service on any machine. + + To avoid such bounce attacks, it is suggested that servers not open + data connections to TCP ports less than 1024. If a server receives a + PORT command containing a TCP port number less than 1024, the + suggested response is 504 (defined as "Command not implemented for + that parameter" by [PR85]). Note that this still leaves non-well + known servers (those running on ports greater than 1023) vulnerable + to bounce attacks. + + Several proposals (e.g., [AOM98] and [Pis94]) provide a mechanism + that would allow data connections to be made using a transport + protocol other than TCP. Similar precautions should be taken to + protect well known services when using these protocols. + + Also note that the bounce attack generally requires that a + perpetrator be able to upload a file to an FTP server and later + download it to the service being attacked. Using proper file + protections will prevent this behavior. However, attackers can also + attack services by sending random data from a remote FTP server which + may cause problems for some services. + + Disabling the PORT command is also an option for protecting against + the bounce attack. Most file transfers can be made using only the + PASV command [Bel94]. The disadvantage of disabling the PORT command + is that one loses the ability to use proxy FTP, but proxy FTP may not + be necessary in a particular environment. + +4 Restricted Access + + For some FTP servers, it is desirable to restrict access based on + network address. For example, a server might want to restrict access + to certain files from certain places (e.g., a certain file should not + be transferred out of an organization). In such a situation, the + server should confirm that the network address of the remote hosts on + both the control connection and the data connection are within the + organization before sending a restricted file. By checking both + connections, a server is protected against the case when the control + connection is established with a trusted host and the data connection + is not. Likewise, the client should verify the IP address of the + remote host after accepting a connection on a port opened in listen + mode to verify that the connection was made by the expected server. + + Note that restricting access based on network address leaves the FTP + server vulnerable to "spoof" attacks. In a spoof attack, for + example, an attacking machine could assume the host address of + another machine inside an organization and download files that are + + + +Allman & Ostermann Informational [Page 3] + +RFC 2577 FTP Security Considerations May 1999 + + + not accessible from outside the organization. Whenever possible, + secure authentication mechanisms should be used, such as those + outlined in [HL97]. + +5 Protecting Passwords + + To minimize the risk of brute force password guessing through the FTP + server, it is suggested that servers limit the number of attempts + that can be made at sending a correct password. After a small number + of attempts (3-5), the server should close the control connection + with the client. Before closing the control connection the server + must send a return code of 421 ("Service not available, closing + control connection." [PR85]) to the client. In addition, it is + suggested that the server impose a 5 second delay before replying to + an invalid "PASS" command to diminish the efficiency of a brute force + attack. If available, mechanisms already provided by the target + operating system should be used to implement the above suggestions. + + An intruder can subvert the above mechanisms by establishing + multiple, parallel control connections to a server. To combat the + use of multiple concurrent connections, the server could either limit + the total number of control connections possible or attempt to detect + suspicious activity across sessions and refuse further connections + from the site. However, both of these mechanisms open the door to + "denial of service" attacks, in which an attacker purposely initiates + the attack to disable access by a valid user. + + Standard FTP [PR85] sends passwords in clear text using the "PASS" + command. It is suggested that FTP clients and servers use alternate + authentication mechanisms that are not subject to eavesdropping (such + as the mechanisms being developed by the IETF Common Authentication + Technology Working Group [HL97]). + +6 Privacy + + All data and control information (including passwords) is sent across + the network in unencrypted form by standard FTP [PR85]. To guarantee + the privacy of the information FTP transmits, a strong encryption + scheme should be used whenever possible. One such mechanism is + defined in [HL97]. + +7 Protecting Usernames + + Standard FTP [PR85] specifies a 530 response to the USER command when + the username is rejected. If the username is valid and a password is + required FTP returns a 331 response instead. In order to prevent a + malicious client from determining valid usernames on a server, it is + suggested that a server always return 331 to the USER command and + + + +Allman & Ostermann Informational [Page 4] + +RFC 2577 FTP Security Considerations May 1999 + + + then reject the combination of username and password for an invalid + username. + +8 Port Stealing + + Many operating systems assign dynamic port numbers in increasing + order. By making a legitimate transfer, an attacker can observe the + current port number allocated by the server and "guess" the next one + that will be used. The attacker can make a connection to this port, + thus denying another legitimate client the ability to make a + transfer. Alternatively, the attacker can steal a file meant for a + legitimate user. In addition, an attacker can insert a forged file + into a data stream thought to come from an authenticated client. + This problem can be mitigated by making FTP clients and servers use + random local port numbers for data connections, either by requesting + random ports from the operating system or using system dependent + mechanisms. + +9 Software-Base Security Problems + + The emphasis in this document is on protocol-related security issues. + There are a number of documented FTP security-related problems that + are due to poor implementation as well. Although the details of + these types of problems are beyond the scope of this document, it + should be pointed out that the following FTP features has been abused + in the past and should be treated with great care by future + implementers: + + Anonymous FTP + + Anonymous FTP refers to the ability of a client to connect to an + FTP server with minimal authentication and gain access to public + files. Security problems arise when such a user can read all + files on the system or can create files. [CERT92:09] [CERT93:06] + + Remote Command Execution + + An optional FTP extension, "SITE EXEC", allows clients to execute + arbitrary commands on the server. This feature should obviously + be implemented with great care. There are several documented + cases of the FTP "SITE EXEC" command being used to subvert server + security [CERT94:08] [CERT95:16] + + Debug Code + + Several previous security compromises related to FTP can be + attributed to software that was installed with debugging features + enabled [CERT88:01]. + + + +Allman & Ostermann Informational [Page 5] + +RFC 2577 FTP Security Considerations May 1999 + + + This document recommends that implementors of FTP servers with these + capabilities review all of the CERT advisories for attacks on these + or similar mechanisms before releasing their software. + +10 Conclusion + + Using the above suggestions can decrease the security problems + associated with FTP servers without eliminating functionality. + +11 Security Considerations + + Security issues are discussed throughout this memo. + +Acknowledgments + + We would like to thank Alex Belits, Jim Bound, William Curtin, Robert + Elz, Paul Hethmon, Alun Jones and Stephen Tihor for their helpful + comments on this paper. Also, we thank the FTPEXT WG members who + gave many useful suggestions at the Memphis IETF meeting. + +References + + [AOM98] Allman, M., Ostermann, S. and C. Metz, "FTP Extensions + for IPv6 and NATs", RFC 2428, September 1998. + + [Bel94] Bellovin. S., "Firewall-Friendly FTP", RFC 1579, February + 1994. + + [CERT88:01] CERT Advisory CA-88:01. ftpd Vulnerability. December, + 1988 ftp://info.cert.org/pub/cert_advisories/ + + [CERT92:09] CERT Advisory CA-92:09. AIX Anonymous FTP Vulnerability. + April 27, 1992. ftp://info.cert.org/pub/cert_advisories/ + + [CERT93:06] CERT Advisory CA-93:06. Wuarchive ftpd Vulnerability. + September 19,1997 + ftp://info.cert.org/pub/cert_advisories/ + + [CERT94:08] CERT Advisory CA-94:08. ftpd Vulnerabilities. September + 23, 1997. ftp://info.cert.org/pub/cert_advisories/ + + [CERT95:16] CERT Advisory CA-95:16. wu-ftpd Misconfiguration + Vulnerability. September 23, 1997 + ftp://info.cert.org/pub/cert_advisories/ + + [CERT97:27] CERT Advisory CA-97.27. FTP Bounce. January 8, 1998. + ftp://info.cert.org/pub/cert_advisories/ + + + + +Allman & Ostermann Informational [Page 6] + +RFC 2577 FTP Security Considerations May 1999 + + + [HL97] Horowitz, M. and S. Lunt, "FTP Security Extensions", RFC + 2228, October 1997. + + [Pis94] Piscitello, D., "FTP Operation Over Big Address Records + (FOOBAR), RFC 1639, June 1994. + + [Pos81] Postel, J., "Transmission Control Protocol", STD 7, RFC + 793, September 1981. + + [PR85] Postel, J. and J. Reynolds, "File Transfer Protocol + (FTP)", STD 9, RFC 959, October 1985. + + [RP94] Reynolds, J. and J. Postel, "Assigned Numbers", STD 2, + RFC 1700, October 1994. See also: + http://www.iana.org/numbers.html + +Authors' Addresses + + Mark Allman + NASA Glenn Research Center/Sterling Software + 21000 Brookpark Rd. MS 54-2 + Cleveland, OH 44135 + + EMail: mallman@grc.nasa.gov + + + Shawn Ostermann + School of Electrical Engineering and Computer Science + Ohio University + 416 Morton Hall + Athens, OH 45701 + + EMail: ostermann@cs.ohiou.edu + + + + + + + + + + + + + + + + + + +Allman & Ostermann Informational [Page 7] + +RFC 2577 FTP Security Considerations May 1999 + + +Full Copyright Statement + + Copyright (C) The Internet Society (1999). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Allman & Ostermann Informational [Page 8] + diff --git a/lib/ftp/doc/archive/rfc959.txt b/lib/ftp/doc/archive/rfc959.txt new file mode 100644 index 0000000000..5c9f11af5d --- /dev/null +++ b/lib/ftp/doc/archive/rfc959.txt @@ -0,0 +1,3933 @@ + + +Network Working Group J. Postel +Request for Comments: 959 J. Reynolds + ISI +Obsoletes RFC: 765 (IEN 149) October 1985 + + FILE TRANSFER PROTOCOL (FTP) + + +Status of this Memo + + This memo is the official specification of the File Transfer + Protocol (FTP). Distribution of this memo is unlimited. + + The following new optional commands are included in this edition of + the specification: + + CDUP (Change to Parent Directory), SMNT (Structure Mount), STOU + (Store Unique), RMD (Remove Directory), MKD (Make Directory), PWD + (Print Directory), and SYST (System). + + Note that this specification is compatible with the previous edition. + +1. INTRODUCTION + + The objectives of FTP are 1) to promote sharing of files (computer + programs and/or data), 2) to encourage indirect or implicit (via + programs) use of remote computers, 3) to shield a user from + variations in file storage systems among hosts, and 4) to transfer + data reliably and efficiently. FTP, though usable directly by a user + at a terminal, is designed mainly for use by programs. + + The attempt in this specification is to satisfy the diverse needs of + users of maxi-hosts, mini-hosts, personal workstations, and TACs, + with a simple, and easily implemented protocol design. + + This paper assumes knowledge of the Transmission Control Protocol + (TCP) [2] and the Telnet Protocol [3]. These documents are contained + in the ARPA-Internet protocol handbook [1]. + +2. OVERVIEW + + In this section, the history, the terminology, and the FTP model are + discussed. The terms defined in this section are only those that + have special significance in FTP. Some of the terminology is very + specific to the FTP model; some readers may wish to turn to the + section on the FTP model while reviewing the terminology. + + + + + + + +Postel & Reynolds [Page 1] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 2.1. HISTORY + + FTP has had a long evolution over the years. Appendix III is a + chronological compilation of Request for Comments documents + relating to FTP. These include the first proposed file transfer + mechanisms in 1971 that were developed for implementation on hosts + at M.I.T. (RFC 114), plus comments and discussion in RFC 141. + + RFC 172 provided a user-level oriented protocol for file transfer + between host computers (including terminal IMPs). A revision of + this as RFC 265, restated FTP for additional review, while RFC 281 + suggested further changes. The use of a "Set Data Type" + transaction was proposed in RFC 294 in January 1982. + + RFC 354 obsoleted RFCs 264 and 265. The File Transfer Protocol + was now defined as a protocol for file transfer between HOSTs on + the ARPANET, with the primary function of FTP defined as + transfering files efficiently and reliably among hosts and + allowing the convenient use of remote file storage capabilities. + RFC 385 further commented on errors, emphasis points, and + additions to the protocol, while RFC 414 provided a status report + on the working server and user FTPs. RFC 430, issued in 1973, + (among other RFCs too numerous to mention) presented further + comments on FTP. Finally, an "official" FTP document was + published as RFC 454. + + By July 1973, considerable changes from the last versions of FTP + were made, but the general structure remained the same. RFC 542 + was published as a new "official" specification to reflect these + changes. However, many implementations based on the older + specification were not updated. + + In 1974, RFCs 607 and 614 continued comments on FTP. RFC 624 + proposed further design changes and minor modifications. In 1975, + RFC 686 entitled, "Leaving Well Enough Alone", discussed the + differences between all of the early and later versions of FTP. + RFC 691 presented a minor revision of RFC 686, regarding the + subject of print files. + + Motivated by the transition from the NCP to the TCP as the + underlying protocol, a phoenix was born out of all of the above + efforts in RFC 765 as the specification of FTP for use on TCP. + + This current edition of the FTP specification is intended to + correct some minor documentation errors, to improve the + explanation of some protocol features, and to add some new + optional commands. + + +Postel & Reynolds [Page 2] + + + +RFC 959 October 1985 +File Transfer Protocol + + + In particular, the following new optional commands are included in + this edition of the specification: + + CDUP - Change to Parent Directory + + SMNT - Structure Mount + + STOU - Store Unique + + RMD - Remove Directory + + MKD - Make Directory + + PWD - Print Directory + + SYST - System + + This specification is compatible with the previous edition. A + program implemented in conformance to the previous specification + should automatically be in conformance to this specification. + + 2.2. TERMINOLOGY + + ASCII + + The ASCII character set is as defined in the ARPA-Internet + Protocol Handbook. In FTP, ASCII characters are defined to be + the lower half of an eight-bit code set (i.e., the most + significant bit is zero). + + access controls + + Access controls define users' access privileges to the use of a + system, and to the files in that system. Access controls are + necessary to prevent unauthorized or accidental use of files. + It is the prerogative of a server-FTP process to invoke access + controls. + + byte size + + There are two byte sizes of interest in FTP: the logical byte + size of the file, and the transfer byte size used for the + transmission of the data. The transfer byte size is always 8 + bits. The transfer byte size is not necessarily the byte size + in which data is to be stored in a system, nor the logical byte + size for interpretation of the structure of the data. + + + +Postel & Reynolds [Page 3] + + + +RFC 959 October 1985 +File Transfer Protocol + + + control connection + + The communication path between the USER-PI and SERVER-PI for + the exchange of commands and replies. This connection follows + the Telnet Protocol. + + data connection + + A full duplex connection over which data is transferred, in a + specified mode and type. The data transferred may be a part of + a file, an entire file or a number of files. The path may be + between a server-DTP and a user-DTP, or between two + server-DTPs. + + data port + + The passive data transfer process "listens" on the data port + for a connection from the active transfer process in order to + open the data connection. + + DTP + + The data transfer process establishes and manages the data + connection. The DTP can be passive or active. + + End-of-Line + + The end-of-line sequence defines the separation of printing + lines. The sequence is Carriage Return, followed by Line Feed. + + EOF + + The end-of-file condition that defines the end of a file being + transferred. + + EOR + + The end-of-record condition that defines the end of a record + being transferred. + + error recovery + + A procedure that allows a user to recover from certain errors + such as failure of either host system or transfer process. In + FTP, error recovery may involve restarting a file transfer at a + given checkpoint. + + + +Postel & Reynolds [Page 4] + + + +RFC 959 October 1985 +File Transfer Protocol + + + FTP commands + + A set of commands that comprise the control information flowing + from the user-FTP to the server-FTP process. + + file + + An ordered set of computer data (including programs), of + arbitrary length, uniquely identified by a pathname. + + mode + + The mode in which data is to be transferred via the data + connection. The mode defines the data format during transfer + including EOR and EOF. The transfer modes defined in FTP are + described in the Section on Transmission Modes. + + NVT + + The Network Virtual Terminal as defined in the Telnet Protocol. + + NVFS + + The Network Virtual File System. A concept which defines a + standard network file system with standard commands and + pathname conventions. + + page + + A file may be structured as a set of independent parts called + pages. FTP supports the transmission of discontinuous files as + independent indexed pages. + + pathname + + Pathname is defined to be the character string which must be + input to a file system by a user in order to identify a file. + Pathname normally contains device and/or directory names, and + file name specification. FTP does not yet specify a standard + pathname convention. Each user must follow the file naming + conventions of the file systems involved in the transfer. + + PI + + The protocol interpreter. The user and server sides of the + protocol have distinct roles implemented in a user-PI and a + server-PI. + + +Postel & Reynolds [Page 5] + + + +RFC 959 October 1985 +File Transfer Protocol + + + record + + A sequential file may be structured as a number of contiguous + parts called records. Record structures are supported by FTP + but a file need not have record structure. + + reply + + A reply is an acknowledgment (positive or negative) sent from + server to user via the control connection in response to FTP + commands. The general form of a reply is a completion code + (including error codes) followed by a text string. The codes + are for use by programs and the text is usually intended for + human users. + + server-DTP + + The data transfer process, in its normal "active" state, + establishes the data connection with the "listening" data port. + It sets up parameters for transfer and storage, and transfers + data on command from its PI. The DTP can be placed in a + "passive" state to listen for, rather than initiate a + connection on the data port. + + server-FTP process + + A process or set of processes which perform the function of + file transfer in cooperation with a user-FTP process and, + possibly, another server. The functions consist of a protocol + interpreter (PI) and a data transfer process (DTP). + + server-PI + + The server protocol interpreter "listens" on Port L for a + connection from a user-PI and establishes a control + communication connection. It receives standard FTP commands + from the user-PI, sends replies, and governs the server-DTP. + + type + + The data representation type used for data transfer and + storage. Type implies certain transformations between the time + of data storage and data transfer. The representation types + defined in FTP are described in the Section on Establishing + Data Connections. + + + + +Postel & Reynolds [Page 6] + + + +RFC 959 October 1985 +File Transfer Protocol + + + user + + A person or a process on behalf of a person wishing to obtain + file transfer service. The human user may interact directly + with a server-FTP process, but use of a user-FTP process is + preferred since the protocol design is weighted towards + automata. + + user-DTP + + The data transfer process "listens" on the data port for a + connection from a server-FTP process. If two servers are + transferring data between them, the user-DTP is inactive. + + user-FTP process + + A set of functions including a protocol interpreter, a data + transfer process and a user interface which together perform + the function of file transfer in cooperation with one or more + server-FTP processes. The user interface allows a local + language to be used in the command-reply dialogue with the + user. + + user-PI + + The user protocol interpreter initiates the control connection + from its port U to the server-FTP process, initiates FTP + commands, and governs the user-DTP if that process is part of + the file transfer. + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 7] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 2.3. THE FTP MODEL + + With the above definitions in mind, the following model (shown in + Figure 1) may be diagrammed for an FTP service. + + ------------- + |/---------\| + || User || -------- + ||Interface|<--->| User | + |\----^----/| -------- + ---------- | | | + |/------\| FTP Commands |/----V----\| + ||Server|<---------------->| User || + || PI || FTP Replies || PI || + |\--^---/| |\----^----/| + | | | | | | + -------- |/--V---\| Data |/----V----\| -------- + | File |<--->|Server|<---------------->| User |<--->| File | + |System| || DTP || Connection || DTP || |System| + -------- |\------/| |\---------/| -------- + ---------- ------------- + + Server-FTP USER-FTP + + NOTES: 1. The data connection may be used in either direction. + 2. The data connection need not exist all of the time. + + Figure 1 Model for FTP Use + + In the model described in Figure 1, the user-protocol interpreter + initiates the control connection. The control connection follows + the Telnet protocol. At the initiation of the user, standard FTP + commands are generated by the user-PI and transmitted to the + server process via the control connection. (The user may + establish a direct control connection to the server-FTP, from a + TAC terminal for example, and generate standard FTP commands + independently, bypassing the user-FTP process.) Standard replies + are sent from the server-PI to the user-PI over the control + connection in response to the commands. + + The FTP commands specify the parameters for the data connection + (data port, transfer mode, representation type, and structure) and + the nature of file system operation (store, retrieve, append, + delete, etc.). The user-DTP or its designate should "listen" on + the specified data port, and the server initiate the data + connection and data transfer in accordance with the specified + parameters. It should be noted that the data port need not be in + + +Postel & Reynolds [Page 8] + + + +RFC 959 October 1985 +File Transfer Protocol + + + the same host that initiates the FTP commands via the control + connection, but the user or the user-FTP process must ensure a + "listen" on the specified data port. It ought to also be noted + that the data connection may be used for simultaneous sending and + receiving. + + In another situation a user might wish to transfer files between + two hosts, neither of which is a local host. The user sets up + control connections to the two servers and then arranges for a + data connection between them. In this manner, control information + is passed to the user-PI but data is transferred between the + server data transfer processes. Following is a model of this + server-server interaction. + + + Control ------------ Control + ---------->| User-FTP |<----------- + | | User-PI | | + | | "C" | | + V ------------ V + -------------- -------------- + | Server-FTP | Data Connection | Server-FTP | + | "A" |<---------------------->| "B" | + -------------- Port (A) Port (B) -------------- + + + Figure 2 + + The protocol requires that the control connections be open while + data transfer is in progress. It is the responsibility of the + user to request the closing of the control connections when + finished using the FTP service, while it is the server who takes + the action. The server may abort data transfer if the control + connections are closed without command. + + The Relationship between FTP and Telnet: + + The FTP uses the Telnet protocol on the control connection. + This can be achieved in two ways: first, the user-PI or the + server-PI may implement the rules of the Telnet Protocol + directly in their own procedures; or, second, the user-PI or + the server-PI may make use of the existing Telnet module in the + system. + + Ease of implementaion, sharing code, and modular programming + argue for the second approach. Efficiency and independence + + + +Postel & Reynolds [Page 9] + + + +RFC 959 October 1985 +File Transfer Protocol + + + argue for the first approach. In practice, FTP relies on very + little of the Telnet Protocol, so the first approach does not + necessarily involve a large amount of code. + +3. DATA TRANSFER FUNCTIONS + + Files are transferred only via the data connection. The control + connection is used for the transfer of commands, which describe the + functions to be performed, and the replies to these commands (see the + Section on FTP Replies). Several commands are concerned with the + transfer of data between hosts. These data transfer commands include + the MODE command which specify how the bits of the data are to be + transmitted, and the STRUcture and TYPE commands, which are used to + define the way in which the data are to be represented. The + transmission and representation are basically independent but the + "Stream" transmission mode is dependent on the file structure + attribute and if "Compressed" transmission mode is used, the nature + of the filler byte depends on the representation type. + + 3.1. DATA REPRESENTATION AND STORAGE + + Data is transferred from a storage device in the sending host to a + storage device in the receiving host. Often it is necessary to + perform certain transformations on the data because data storage + representations in the two systems are different. For example, + NVT-ASCII has different data storage representations in different + systems. DEC TOPS-20s's generally store NVT-ASCII as five 7-bit + ASCII characters, left-justified in a 36-bit word. IBM Mainframe's + store NVT-ASCII as 8-bit EBCDIC codes. Multics stores NVT-ASCII + as four 9-bit characters in a 36-bit word. It is desirable to + convert characters into the standard NVT-ASCII representation when + transmitting text between dissimilar systems. The sending and + receiving sites would have to perform the necessary + transformations between the standard representation and their + internal representations. + + A different problem in representation arises when transmitting + binary data (not character codes) between host systems with + different word lengths. It is not always clear how the sender + should send data, and the receiver store it. For example, when + transmitting 32-bit bytes from a 32-bit word-length system to a + 36-bit word-length system, it may be desirable (for reasons of + efficiency and usefulness) to store the 32-bit bytes + right-justified in a 36-bit word in the latter system. In any + case, the user should have the option of specifying data + representation and transformation functions. It should be noted + + + +Postel & Reynolds [Page 10] + + + +RFC 959 October 1985 +File Transfer Protocol + + + that FTP provides for very limited data type representations. + Transformations desired beyond this limited capability should be + performed by the user directly. + + 3.1.1. DATA TYPES + + Data representations are handled in FTP by a user specifying a + representation type. This type may implicitly (as in ASCII or + EBCDIC) or explicitly (as in Local byte) define a byte size for + interpretation which is referred to as the "logical byte size." + Note that this has nothing to do with the byte size used for + transmission over the data connection, called the "transfer + byte size", and the two should not be confused. For example, + NVT-ASCII has a logical byte size of 8 bits. If the type is + Local byte, then the TYPE command has an obligatory second + parameter specifying the logical byte size. The transfer byte + size is always 8 bits. + + 3.1.1.1. ASCII TYPE + + This is the default type and must be accepted by all FTP + implementations. It is intended primarily for the transfer + of text files, except when both hosts would find the EBCDIC + type more convenient. + + The sender converts the data from an internal character + representation to the standard 8-bit NVT-ASCII + representation (see the Telnet specification). The receiver + will convert the data from the standard form to his own + internal form. + + In accordance with the NVT standard, the sequence + should be used where necessary to denote the end of a line + of text. (See the discussion of file structure at the end + of the Section on Data Representation and Storage.) + + Using the standard NVT-ASCII representation means that data + must be interpreted as 8-bit bytes. + + The Format parameter for ASCII and EBCDIC types is discussed + below. + + + + + + + + +Postel & Reynolds [Page 11] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 3.1.1.2. EBCDIC TYPE + + This type is intended for efficient transfer between hosts + which use EBCDIC for their internal character + representation. + + For transmission, the data are represented as 8-bit EBCDIC + characters. The character code is the only difference + between the functional specifications of EBCDIC and ASCII + types. + + End-of-line (as opposed to end-of-record--see the discussion + of structure) will probably be rarely used with EBCDIC type + for purposes of denoting structure, but where it is + necessary the character should be used. + + 3.1.1.3. IMAGE TYPE + + The data are sent as contiguous bits which, for transfer, + are packed into the 8-bit transfer bytes. The receiving + site must store the data as contiguous bits. The structure + of the storage system might necessitate the padding of the + file (or of each record, for a record-structured file) to + some convenient boundary (byte, word or block). This + padding, which must be all zeros, may occur only at the end + of the file (or at the end of each record) and there must be + a way of identifying the padding bits so that they may be + stripped off if the file is retrieved. The padding + transformation should be well publicized to enable a user to + process a file at the storage site. + + Image type is intended for the efficient storage and + retrieval of files and for the transfer of binary data. It + is recommended that this type be accepted by all FTP + implementations. + + 3.1.1.4. LOCAL TYPE + + The data is transferred in logical bytes of the size + specified by the obligatory second parameter, Byte size. + The value of Byte size must be a decimal integer; there is + no default value. The logical byte size is not necessarily + the same as the transfer byte size. If there is a + difference in byte sizes, then the logical bytes should be + packed contiguously, disregarding transfer byte boundaries + and with any necessary padding at the end. + + + +Postel & Reynolds [Page 12] + + + +RFC 959 October 1985 +File Transfer Protocol + + + When the data reaches the receiving host, it will be + transformed in a manner dependent on the logical byte size + and the particular host. This transformation must be + invertible (i.e., an identical file can be retrieved if the + same parameters are used) and should be well publicized by + the FTP implementors. + + For example, a user sending 36-bit floating-point numbers to + a host with a 32-bit word could send that data as Local byte + with a logical byte size of 36. The receiving host would + then be expected to store the logical bytes so that they + could be easily manipulated; in this example putting the + 36-bit logical bytes into 64-bit double words should + suffice. + + In another example, a pair of hosts with a 36-bit word size + may send data to one another in words by using TYPE L 36. + The data would be sent in the 8-bit transmission bytes + packed so that 9 transmission bytes carried two host words. + + 3.1.1.5. FORMAT CONTROL + + The types ASCII and EBCDIC also take a second (optional) + parameter; this is to indicate what kind of vertical format + control, if any, is associated with a file. The following + data representation types are defined in FTP: + + A character file may be transferred to a host for one of + three purposes: for printing, for storage and later + retrieval, or for processing. If a file is sent for + printing, the receiving host must know how the vertical + format control is represented. In the second case, it must + be possible to store a file at a host and then retrieve it + later in exactly the same form. Finally, it should be + possible to move a file from one host to another and process + the file at the second host without undue trouble. A single + ASCII or EBCDIC format does not satisfy all these + conditions. Therefore, these types have a second parameter + specifying one of the following three formats: + + 3.1.1.5.1. NON PRINT + + This is the default format to be used if the second + (format) parameter is omitted. Non-print format must be + accepted by all FTP implementations. + + + + +Postel & Reynolds [Page 13] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The file need contain no vertical format information. If + it is passed to a printer process, this process may + assume standard values for spacing and margins. + + Normally, this format will be used with files destined + for processing or just storage. + + 3.1.1.5.2. TELNET FORMAT CONTROLS + + The file contains ASCII/EBCDIC vertical format controls + (i.e., , , , , ) which the printer + process will interpret appropriately. , in exactly + this sequence, also denotes end-of-line. + + 3.1.1.5.2. CARRIAGE CONTROL (ASA) + + The file contains ASA (FORTRAN) vertical format control + characters. (See RFC 740 Appendix C; and Communications + of the ACM, Vol. 7, No. 10, p. 606, October 1964.) In a + line or a record formatted according to the ASA Standard, + the first character is not to be printed. Instead, it + should be used to determine the vertical movement of the + paper which should take place before the rest of the + record is printed. + + The ASA Standard specifies the following control + characters: + + Character Vertical Spacing + + blank Move paper up one line + 0 Move paper up two lines + 1 Move paper to top of next page + + No movement, i.e., overprint + + Clearly there must be some way for a printer process to + distinguish the end of the structural entity. If a file + has record structure (see below) this is no problem; + records will be explicitly marked during transfer and + storage. If the file has no record structure, the + end-of-line sequence is used to separate printing lines, + but these format effectors are overridden by the ASA + controls. + + + + + + +Postel & Reynolds [Page 14] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 3.1.2. DATA STRUCTURES + + In addition to different representation types, FTP allows the + structure of a file to be specified. Three file structures are + defined in FTP: + + file-structure, where there is no internal structure and + the file is considered to be a + continuous sequence of data bytes, + + record-structure, where the file is made up of sequential + records, + + and page-structure, where the file is made up of independent + indexed pages. + + File-structure is the default to be assumed if the STRUcture + command has not been used but both file and record structures + must be accepted for "text" files (i.e., files with TYPE ASCII + or EBCDIC) by all FTP implementations. The structure of a file + will affect both the transfer mode of a file (see the Section + on Transmission Modes) and the interpretation and storage of + the file. + + The "natural" structure of a file will depend on which host + stores the file. A source-code file will usually be stored on + an IBM Mainframe in fixed length records but on a DEC TOPS-20 + as a stream of characters partitioned into lines, for example + by . If the transfer of files between such disparate + sites is to be useful, there must be some way for one site to + recognize the other's assumptions about the file. + + With some sites being naturally file-oriented and others + naturally record-oriented there may be problems if a file with + one structure is sent to a host oriented to the other. If a + text file is sent with record-structure to a host which is file + oriented, then that host should apply an internal + transformation to the file based on the record structure. + Obviously, this transformation should be useful, but it must + also be invertible so that an identical file may be retrieved + using record structure. + + In the case of a file being sent with file-structure to a + record-oriented host, there exists the question of what + criteria the host should use to divide the file into records + which can be processed locally. If this division is necessary, + the FTP implementation should use the end-of-line sequence, + + +Postel & Reynolds [Page 15] + + + +RFC 959 October 1985 +File Transfer Protocol + + + for ASCII, or for EBCDIC text files, as the + delimiter. If an FTP implementation adopts this technique, it + must be prepared to reverse the transformation if the file is + retrieved with file-structure. + + 3.1.2.1. FILE STRUCTURE + + File structure is the default to be assumed if the STRUcture + command has not been used. + + In file-structure there is no internal structure and the + file is considered to be a continuous sequence of data + bytes. + + 3.1.2.2. RECORD STRUCTURE + + Record structures must be accepted for "text" files (i.e., + files with TYPE ASCII or EBCDIC) by all FTP implementations. + + In record-structure the file is made up of sequential + records. + + 3.1.2.3. PAGE STRUCTURE + + To transmit files that are discontinuous, FTP defines a page + structure. Files of this type are sometimes known as + "random access files" or even as "holey files". In these + files there is sometimes other information associated with + the file as a whole (e.g., a file descriptor), or with a + section of the file (e.g., page access controls), or both. + In FTP, the sections of the file are called pages. + + To provide for various page sizes and associated + information, each page is sent with a page header. The page + header has the following defined fields: + + Header Length + + The number of logical bytes in the page header + including this byte. The minimum header length is 4. + + Page Index + + The logical page number of this section of the file. + This is not the transmission sequence number of this + page, but the index used to identify this page of the + file. + + +Postel & Reynolds [Page 16] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Data Length + + The number of logical bytes in the page data. The + minimum data length is 0. + + Page Type + + The type of page this is. The following page types + are defined: + + 0 = Last Page + + This is used to indicate the end of a paged + structured transmission. The header length must + be 4, and the data length must be 0. + + 1 = Simple Page + + This is the normal type for simple paged files + with no page level associated control + information. The header length must be 4. + + 2 = Descriptor Page + + This type is used to transmit the descriptive + information for the file as a whole. + + 3 = Access Controlled Page + + This type includes an additional header field + for paged files with page level access control + information. The header length must be 5. + + Optional Fields + + Further header fields may be used to supply per page + control information, for example, per page access + control. + + All fields are one logical byte in length. The logical byte + size is specified by the TYPE command. See Appendix I for + further details and a specific case at the page structure. + + A note of caution about parameters: a file must be stored and + retrieved with the same parameters if the retrieved version is to + + + + +Postel & Reynolds [Page 17] + + + +RFC 959 October 1985 +File Transfer Protocol + + + be identical to the version originally transmitted. Conversely, + FTP implementations must return a file identical to the original + if the parameters used to store and retrieve a file are the same. + + 3.2. ESTABLISHING DATA CONNECTIONS + + The mechanics of transferring data consists of setting up the data + connection to the appropriate ports and choosing the parameters + for transfer. Both the user and the server-DTPs have a default + data port. The user-process default data port is the same as the + control connection port (i.e., U). The server-process default + data port is the port adjacent to the control connection port + (i.e., L-1). + + The transfer byte size is 8-bit bytes. This byte size is relevant + only for the actual transfer of the data; it has no bearing on + representation of the data within a host's file system. + + The passive data transfer process (this may be a user-DTP or a + second server-DTP) shall "listen" on the data port prior to + sending a transfer request command. The FTP request command + determines the direction of the data transfer. The server, upon + receiving the transfer request, will initiate the data connection + to the port. When the connection is established, the data + transfer begins between DTP's, and the server-PI sends a + confirming reply to the user-PI. + + Every FTP implementation must support the use of the default data + ports, and only the USER-PI can initiate a change to non-default + ports. + + It is possible for the user to specify an alternate data port by + use of the PORT command. The user may want a file dumped on a TAC + line printer or retrieved from a third party host. In the latter + case, the user-PI sets up control connections with both + server-PI's. One server is then told (by an FTP command) to + "listen" for a connection which the other will initiate. The + user-PI sends one server-PI a PORT command indicating the data + port of the other. Finally, both are sent the appropriate + transfer commands. The exact sequence of commands and replies + sent between the user-controller and the servers is defined in the + Section on FTP Replies. + + In general, it is the server's responsibility to maintain the data + connection--to initiate it and to close it. The exception to this + + + + +Postel & Reynolds [Page 18] + + + +RFC 959 October 1985 +File Transfer Protocol + + + is when the user-DTP is sending the data in a transfer mode that + requires the connection to be closed to indicate EOF. The server + MUST close the data connection under the following conditions: + + 1. The server has completed sending data in a transfer mode + that requires a close to indicate EOF. + + 2. The server receives an ABORT command from the user. + + 3. The port specification is changed by a command from the + user. + + 4. The control connection is closed legally or otherwise. + + 5. An irrecoverable error condition occurs. + + Otherwise the close is a server option, the exercise of which the + server must indicate to the user-process by either a 250 or 226 + reply only. + + 3.3. DATA CONNECTION MANAGEMENT + + Default Data Connection Ports: All FTP implementations must + support use of the default data connection ports, and only the + User-PI may initiate the use of non-default ports. + + Negotiating Non-Default Data Ports: The User-PI may specify a + non-default user side data port with the PORT command. The + User-PI may request the server side to identify a non-default + server side data port with the PASV command. Since a connection + is defined by the pair of addresses, either of these actions is + enough to get a different data connection, still it is permitted + to do both commands to use new ports on both ends of the data + connection. + + Reuse of the Data Connection: When using the stream mode of data + transfer the end of the file must be indicated by closing the + connection. This causes a problem if multiple files are to be + transfered in the session, due to need for TCP to hold the + connection record for a time out period to guarantee the reliable + communication. Thus the connection can not be reopened at once. + + There are two solutions to this problem. The first is to + negotiate a non-default port. The second is to use another + transfer mode. + + A comment on transfer modes. The stream transfer mode is + + +Postel & Reynolds [Page 19] + + + +RFC 959 October 1985 +File Transfer Protocol + + + inherently unreliable, since one can not determine if the + connection closed prematurely or not. The other transfer modes + (Block, Compressed) do not close the connection to indicate the + end of file. They have enough FTP encoding that the data + connection can be parsed to determine the end of the file. + Thus using these modes one can leave the data connection open + for multiple file transfers. + + 3.4. TRANSMISSION MODES + + The next consideration in transferring data is choosing the + appropriate transmission mode. There are three modes: one which + formats the data and allows for restart procedures; one which also + compresses the data for efficient transfer; and one which passes + the data with little or no processing. In this last case the mode + interacts with the structure attribute to determine the type of + processing. In the compressed mode, the representation type + determines the filler byte. + + All data transfers must be completed with an end-of-file (EOF) + which may be explicitly stated or implied by the closing of the + data connection. For files with record structure, all the + end-of-record markers (EOR) are explicit, including the final one. + For files transmitted in page structure a "last-page" page type is + used. + + NOTE: In the rest of this section, byte means "transfer byte" + except where explicitly stated otherwise. + + For the purpose of standardized transfer, the sending host will + translate its internal end of line or end of record denotation + into the representation prescribed by the transfer mode and file + structure, and the receiving host will perform the inverse + translation to its internal denotation. An IBM Mainframe record + count field may not be recognized at another host, so the + end-of-record information may be transferred as a two byte control + code in Stream mode or as a flagged bit in a Block or Compressed + mode descriptor. End-of-line in an ASCII or EBCDIC file with no + record structure should be indicated by or , + respectively. Since these transformations imply extra work for + some systems, identical systems transferring non-record structured + text files might wish to use a binary representation and stream + mode for the transfer. + + + + + + +Postel & Reynolds [Page 20] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The following transmission modes are defined in FTP: + + 3.4.1. STREAM MODE + + The data is transmitted as a stream of bytes. There is no + restriction on the representation type used; record structures + are allowed. + + In a record structured file EOR and EOF will each be indicated + by a two-byte control code. The first byte of the control code + will be all ones, the escape character. The second byte will + have the low order bit on and zeros elsewhere for EOR and the + second low order bit on for EOF; that is, the byte will have + value 1 for EOR and value 2 for EOF. EOR and EOF may be + indicated together on the last byte transmitted by turning both + low order bits on (i.e., the value 3). If a byte of all ones + was intended to be sent as data, it should be repeated in the + second byte of the control code. + + If the structure is a file structure, the EOF is indicated by + the sending host closing the data connection and all bytes are + data bytes. + + 3.4.2. BLOCK MODE + + The file is transmitted as a series of data blocks preceded by + one or more header bytes. The header bytes contain a count + field, and descriptor code. The count field indicates the + total length of the data block in bytes, thus marking the + beginning of the next data block (there are no filler bits). + The descriptor code defines: last block in the file (EOF) last + block in the record (EOR), restart marker (see the Section on + Error Recovery and Restart) or suspect data (i.e., the data + being transferred is suspected of errors and is not reliable). + This last code is NOT intended for error control within FTP. + It is motivated by the desire of sites exchanging certain types + of data (e.g., seismic or weather data) to send and receive all + the data despite local errors (such as "magnetic tape read + errors"), but to indicate in the transmission that certain + portions are suspect). Record structures are allowed in this + mode, and any representation type may be used. + + The header consists of the three bytes. Of the 24 bits of + header information, the 16 low order bits shall represent byte + count, and the 8 high order bits shall represent descriptor + codes as shown below. + + + +Postel & Reynolds [Page 21] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Block Header + + +----------------+----------------+----------------+ + | Descriptor | Byte Count | + | 8 bits | 16 bits | + +----------------+----------------+----------------+ + + + The descriptor codes are indicated by bit flags in the + descriptor byte. Four codes have been assigned, where each + code number is the decimal value of the corresponding bit in + the byte. + + Code Meaning + + 128 End of data block is EOR + 64 End of data block is EOF + 32 Suspected errors in data block + 16 Data block is a restart marker + + With this encoding, more than one descriptor coded condition + may exist for a particular block. As many bits as necessary + may be flagged. + + The restart marker is embedded in the data stream as an + integral number of 8-bit bytes representing printable + characters in the language being used over the control + connection (e.g., default--NVT-ASCII). (Space, in the + appropriate language) must not be used WITHIN a restart marker. + + For example, to transmit a six-character marker, the following + would be sent: + + +--------+--------+--------+ + |Descrptr| Byte count | + |code= 16| = 6 | + +--------+--------+--------+ + + +--------+--------+--------+ + | Marker | Marker | Marker | + | 8 bits | 8 bits | 8 bits | + +--------+--------+--------+ + + +--------+--------+--------+ + | Marker | Marker | Marker | + | 8 bits | 8 bits | 8 bits | + +--------+--------+--------+ + + +Postel & Reynolds [Page 22] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 3.4.3. COMPRESSED MODE + + There are three kinds of information to be sent: regular data, + sent in a byte string; compressed data, consisting of + replications or filler; and control information, sent in a + two-byte escape sequence. If n>0 bytes (up to 127) of regular + data are sent, these n bytes are preceded by a byte with the + left-most bit set to 0 and the right-most 7 bits containing the + number n. + + Byte string: + + 1 7 8 8 + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + |0| n | | d(1) | ... | d(n) | + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + ^ ^ + |---n bytes---| + of data + + String of n data bytes d(1),..., d(n) + Count n must be positive. + + To compress a string of n replications of the data byte d, the + following 2 bytes are sent: + + Replicated Byte: + + 2 6 8 + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + |1 0| n | | d | + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + + A string of n filler bytes can be compressed into a single + byte, where the filler byte varies with the representation + type. If the type is ASCII or EBCDIC the filler byte is + (Space, ASCII code 32, EBCDIC code 64). If the type is Image + or Local byte the filler is a zero byte. + + Filler String: + + 2 6 + +-+-+-+-+-+-+-+-+ + |1 1| n | + +-+-+-+-+-+-+-+-+ + + The escape sequence is a double byte, the first of which is the + + +Postel & Reynolds [Page 23] + + + +RFC 959 October 1985 +File Transfer Protocol + + + escape byte (all zeros) and the second of which contains + descriptor codes as defined in Block mode. The descriptor + codes have the same meaning as in Block mode and apply to the + succeeding string of bytes. + + Compressed mode is useful for obtaining increased bandwidth on + very large network transmissions at a little extra CPU cost. + It can be most effectively used to reduce the size of printer + files such as those generated by RJE hosts. + + 3.5. ERROR RECOVERY AND RESTART + + There is no provision for detecting bits lost or scrambled in data + transfer; this level of error control is handled by the TCP. + However, a restart procedure is provided to protect users from + gross system failures (including failures of a host, an + FTP-process, or the underlying network). + + The restart procedure is defined only for the block and compressed + modes of data transfer. It requires the sender of data to insert + a special marker code in the data stream with some marker + information. The marker information has meaning only to the + sender, but must consist of printable characters in the default or + negotiated language of the control connection (ASCII or EBCDIC). + The marker could represent a bit-count, a record-count, or any + other information by which a system may identify a data + checkpoint. The receiver of data, if it implements the restart + procedure, would then mark the corresponding position of this + marker in the receiving system, and return this information to the + user. + + In the event of a system failure, the user can restart the data + transfer by identifying the marker point with the FTP restart + procedure. The following example illustrates the use of the + restart procedure. + + The sender of the data inserts an appropriate marker block in the + data stream at a convenient point. The receiving host marks the + corresponding data point in its file system and conveys the last + known sender and receiver marker information to the user, either + directly or over the control connection in a 110 reply (depending + on who is the sender). In the event of a system failure, the user + or controller process restarts the server at the last server + marker by sending a restart command with server's marker code as + its argument. The restart command is transmitted over the control + + + + +Postel & Reynolds [Page 24] + + + +RFC 959 October 1985 +File Transfer Protocol + + + connection and is immediately followed by the command (such as + RETR, STOR or LIST) which was being executed when the system + failure occurred. + +4. FILE TRANSFER FUNCTIONS + + The communication channel from the user-PI to the server-PI is + established as a TCP connection from the user to the standard server + port. The user protocol interpreter is responsible for sending FTP + commands and interpreting the replies received; the server-PI + interprets commands, sends replies and directs its DTP to set up the + data connection and transfer the data. If the second party to the + data transfer (the passive transfer process) is the user-DTP, then it + is governed through the internal protocol of the user-FTP host; if it + is a second server-DTP, then it is governed by its PI on command from + the user-PI. The FTP replies are discussed in the next section. In + the description of a few of the commands in this section, it is + helpful to be explicit about the possible replies. + + 4.1. FTP COMMANDS + + 4.1.1. ACCESS CONTROL COMMANDS + + The following commands specify access control identifiers + (command codes are shown in parentheses). + + USER NAME (USER) + + The argument field is a Telnet string identifying the user. + The user identification is that which is required by the + server for access to its file system. This command will + normally be the first command transmitted by the user after + the control connections are made (some servers may require + this). Additional identification information in the form of + a password and/or an account command may also be required by + some servers. Servers may allow a new USER command to be + entered at any point in order to change the access control + and/or accounting information. This has the effect of + flushing any user, password, and account information already + supplied and beginning the login sequence again. All + transfer parameters are unchanged and any file transfer in + progress is completed under the old access control + parameters. + + + + + + +Postel & Reynolds [Page 25] + + + +RFC 959 October 1985 +File Transfer Protocol + + + PASSWORD (PASS) + + The argument field is a Telnet string specifying the user's + password. This command must be immediately preceded by the + user name command, and, for some sites, completes the user's + identification for access control. Since password + information is quite sensitive, it is desirable in general + to "mask" it or suppress typeout. It appears that the + server has no foolproof way to achieve this. It is + therefore the responsibility of the user-FTP process to hide + the sensitive password information. + + ACCOUNT (ACCT) + + The argument field is a Telnet string identifying the user's + account. The command is not necessarily related to the USER + command, as some sites may require an account for login and + others only for specific access, such as storing files. In + the latter case the command may arrive at any time. + + There are reply codes to differentiate these cases for the + automation: when account information is required for login, + the response to a successful PASSword command is reply code + 332. On the other hand, if account information is NOT + required for login, the reply to a successful PASSword + command is 230; and if the account information is needed for + a command issued later in the dialogue, the server should + return a 332 or 532 reply depending on whether it stores + (pending receipt of the ACCounT command) or discards the + command, respectively. + + CHANGE WORKING DIRECTORY (CWD) + + This command allows the user to work with a different + directory or dataset for file storage or retrieval without + altering his login or accounting information. Transfer + parameters are similarly unchanged. The argument is a + pathname specifying a directory or other system dependent + file group designator. + + CHANGE TO PARENT DIRECTORY (CDUP) + + This command is a special case of CWD, and is included to + simplify the implementation of programs for transferring + directory trees between operating systems having different + + + + +Postel & Reynolds [Page 26] + + + +RFC 959 October 1985 +File Transfer Protocol + + + syntaxes for naming the parent directory. The reply codes + shall be identical to the reply codes of CWD. See + Appendix II for further details. + + STRUCTURE MOUNT (SMNT) + + This command allows the user to mount a different file + system data structure without altering his login or + accounting information. Transfer parameters are similarly + unchanged. The argument is a pathname specifying a + directory or other system dependent file group designator. + + REINITIALIZE (REIN) + + This command terminates a USER, flushing all I/O and account + information, except to allow any transfer in progress to be + completed. All parameters are reset to the default settings + and the control connection is left open. This is identical + to the state in which a user finds himself immediately after + the control connection is opened. A USER command may be + expected to follow. + + LOGOUT (QUIT) + + This command terminates a USER and if file transfer is not + in progress, the server closes the control connection. If + file transfer is in progress, the connection will remain + open for result response and the server will then close it. + If the user-process is transferring files for several USERs + but does not wish to close and then reopen connections for + each, then the REIN command should be used instead of QUIT. + + An unexpected close on the control connection will cause the + server to take the effective action of an abort (ABOR) and a + logout (QUIT). + + 4.1.2. TRANSFER PARAMETER COMMANDS + + All data transfer parameters have default values, and the + commands specifying data transfer parameters are required only + if the default parameter values are to be changed. The default + value is the last specified value, or if no value has been + specified, the standard default value is as stated here. This + implies that the server must "remember" the applicable default + values. The commands may be in any order except that they must + precede the FTP service request. The following commands + specify data transfer parameters: + + +Postel & Reynolds [Page 27] + + + +RFC 959 October 1985 +File Transfer Protocol + + + DATA PORT (PORT) + + The argument is a HOST-PORT specification for the data port + to be used in data connection. There are defaults for both + the user and server data ports, and under normal + circumstances this command and its reply are not needed. If + this command is used, the argument is the concatenation of a + 32-bit internet host address and a 16-bit TCP port address. + This address information is broken into 8-bit fields and the + value of each field is transmitted as a decimal number (in + character string representation). The fields are separated + by commas. A port command would be: + + PORT h1,h2,h3,h4,p1,p2 + + where h1 is the high order 8 bits of the internet host + address. + + PASSIVE (PASV) + + This command requests the server-DTP to "listen" on a data + port (which is not its default data port) and to wait for a + connection rather than initiate one upon receipt of a + transfer command. The response to this command includes the + host and port address this server is listening on. + + REPRESENTATION TYPE (TYPE) + + The argument specifies the representation type as described + in the Section on Data Representation and Storage. Several + types take a second parameter. The first parameter is + denoted by a single Telnet character, as is the second + Format parameter for ASCII and EBCDIC; the second parameter + for local byte is a decimal integer to indicate Bytesize. + The parameters are separated by a (Space, ASCII code + 32). + + The following codes are assigned for type: + + \ / + A - ASCII | | N - Non-print + |-><-| T - Telnet format effectors + E - EBCDIC| | C - Carriage Control (ASA) + / \ + I - Image + + L - Local byte Byte size + + +Postel & Reynolds [Page 28] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The default representation type is ASCII Non-print. If the + Format parameter is changed, and later just the first + argument is changed, Format then returns to the Non-print + default. + + FILE STRUCTURE (STRU) + + The argument is a single Telnet character code specifying + file structure described in the Section on Data + Representation and Storage. + + The following codes are assigned for structure: + + F - File (no record structure) + R - Record structure + P - Page structure + + The default structure is File. + + TRANSFER MODE (MODE) + + The argument is a single Telnet character code specifying + the data transfer modes described in the Section on + Transmission Modes. + + The following codes are assigned for transfer modes: + + S - Stream + B - Block + C - Compressed + + The default transfer mode is Stream. + + 4.1.3. FTP SERVICE COMMANDS + + The FTP service commands define the file transfer or the file + system function requested by the user. The argument of an FTP + service command will normally be a pathname. The syntax of + pathnames must conform to server site conventions (with + standard defaults applicable), and the language conventions of + the control connection. The suggested default handling is to + use the last specified device, directory or file name, or the + standard default defined for local users. The commands may be + in any order except that a "rename from" command must be + followed by a "rename to" command and the restart command must + be followed by the interrupted service command (e.g., STOR or + RETR). The data, when transferred in response to FTP service + + +Postel & Reynolds [Page 29] + + + +RFC 959 October 1985 +File Transfer Protocol + + + commands, shall always be sent over the data connection, except + for certain informative replies. The following commands + specify FTP service requests: + + RETRIEVE (RETR) + + This command causes the server-DTP to transfer a copy of the + file, specified in the pathname, to the server- or user-DTP + at the other end of the data connection. The status and + contents of the file at the server site shall be unaffected. + + STORE (STOR) + + This command causes the server-DTP to accept the data + transferred via the data connection and to store the data as + a file at the server site. If the file specified in the + pathname exists at the server site, then its contents shall + be replaced by the data being transferred. A new file is + created at the server site if the file specified in the + pathname does not already exist. + + STORE UNIQUE (STOU) + + This command behaves like STOR except that the resultant + file is to be created in the current directory under a name + unique to that directory. The 250 Transfer Started response + must include the name generated. + + APPEND (with create) (APPE) + + This command causes the server-DTP to accept the data + transferred via the data connection and to store the data in + a file at the server site. If the file specified in the + pathname exists at the server site, then the data shall be + appended to that file; otherwise the file specified in the + pathname shall be created at the server site. + + ALLOCATE (ALLO) + + This command may be required by some servers to reserve + sufficient storage to accommodate the new file to be + transferred. The argument shall be a decimal integer + representing the number of bytes (using the logical byte + size) of storage to be reserved for the file. For files + sent with record or page structure a maximum record or page + size (in logical bytes) might also be necessary; this is + indicated by a decimal integer in a second argument field of + + +Postel & Reynolds [Page 30] + + + +RFC 959 October 1985 +File Transfer Protocol + + + the command. This second argument is optional, but when + present should be separated from the first by the three + Telnet characters R . This command shall be + followed by a STORe or APPEnd command. The ALLO command + should be treated as a NOOP (no operation) by those servers + which do not require that the maximum size of the file be + declared beforehand, and those servers interested in only + the maximum record or page size should accept a dummy value + in the first argument and ignore it. + + RESTART (REST) + + The argument field represents the server marker at which + file transfer is to be restarted. This command does not + cause file transfer but skips over the file to the specified + data checkpoint. This command shall be immediately followed + by the appropriate FTP service command which shall cause + file transfer to resume. + + RENAME FROM (RNFR) + + This command specifies the old pathname of the file which is + to be renamed. This command must be immediately followed by + a "rename to" command specifying the new file pathname. + + RENAME TO (RNTO) + + This command specifies the new pathname of the file + specified in the immediately preceding "rename from" + command. Together the two commands cause a file to be + renamed. + + ABORT (ABOR) + + This command tells the server to abort the previous FTP + service command and any associated transfer of data. The + abort command may require "special action", as discussed in + the Section on FTP Commands, to force recognition by the + server. No action is to be taken if the previous command + has been completed (including data transfer). The control + connection is not to be closed by the server, but the data + connection must be closed. + + There are two cases for the server upon receipt of this + command: (1) the FTP service command was already completed, + or (2) the FTP service command is still in progress. + + + +Postel & Reynolds [Page 31] + + + +RFC 959 October 1985 +File Transfer Protocol + + + In the first case, the server closes the data connection + (if it is open) and responds with a 226 reply, indicating + that the abort command was successfully processed. + + In the second case, the server aborts the FTP service in + progress and closes the data connection, returning a 426 + reply to indicate that the service request terminated + abnormally. The server then sends a 226 reply, + indicating that the abort command was successfully + processed. + + DELETE (DELE) + + This command causes the file specified in the pathname to be + deleted at the server site. If an extra level of protection + is desired (such as the query, "Do you really wish to + delete?"), it should be provided by the user-FTP process. + + REMOVE DIRECTORY (RMD) + + This command causes the directory specified in the pathname + to be removed as a directory (if the pathname is absolute) + or as a subdirectory of the current working directory (if + the pathname is relative). See Appendix II. + + MAKE DIRECTORY (MKD) + + This command causes the directory specified in the pathname + to be created as a directory (if the pathname is absolute) + or as a subdirectory of the current working directory (if + the pathname is relative). See Appendix II. + + PRINT WORKING DIRECTORY (PWD) + + This command causes the name of the current working + directory to be returned in the reply. See Appendix II. + + LIST (LIST) + + This command causes a list to be sent from the server to the + passive DTP. If the pathname specifies a directory or other + group of files, the server should transfer a list of files + in the specified directory. If the pathname specifies a + file then the server should send current information on the + file. A null argument implies the user's current working or + default directory. The data transfer is over the data + connection in type ASCII or type EBCDIC. (The user must + + +Postel & Reynolds [Page 32] + + + +RFC 959 October 1985 +File Transfer Protocol + + + ensure that the TYPE is appropriately ASCII or EBCDIC). + Since the information on a file may vary widely from system + to system, this information may be hard to use automatically + in a program, but may be quite useful to a human user. + + NAME LIST (NLST) + + This command causes a directory listing to be sent from + server to user site. The pathname should specify a + directory or other system-specific file group descriptor; a + null argument implies the current directory. The server + will return a stream of names of files and no other + information. The data will be transferred in ASCII or + EBCDIC type over the data connection as valid pathname + strings separated by or . (Again the user must + ensure that the TYPE is correct.) This command is intended + to return information that can be used by a program to + further process the files automatically. For example, in + the implementation of a "multiple get" function. + + SITE PARAMETERS (SITE) + + This command is used by the server to provide services + specific to his system that are essential to file transfer + but not sufficiently universal to be included as commands in + the protocol. The nature of these services and the + specification of their syntax can be stated in a reply to + the HELP SITE command. + + SYSTEM (SYST) + + This command is used to find out the type of operating + system at the server. The reply shall have as its first + word one of the system names listed in the current version + of the Assigned Numbers document [4]. + + STATUS (STAT) + + This command shall cause a status response to be sent over + the control connection in the form of a reply. The command + may be sent during a file transfer (along with the Telnet IP + and Synch signals--see the Section on FTP Commands) in which + case the server will respond with the status of the + operation in progress, or it may be sent between file + transfers. In the latter case, the command may have an + argument field. If the argument is a pathname, the command + is analogous to the "list" command except that data shall be + + +Postel & Reynolds [Page 33] + + + +RFC 959 October 1985 +File Transfer Protocol + + + transferred over the control connection. If a partial + pathname is given, the server may respond with a list of + file names or attributes associated with that specification. + If no argument is given, the server should return general + status information about the server FTP process. This + should include current values of all transfer parameters and + the status of connections. + + HELP (HELP) + + This command shall cause the server to send helpful + information regarding its implementation status over the + control connection to the user. The command may take an + argument (e.g., any command name) and return more specific + information as a response. The reply is type 211 or 214. + It is suggested that HELP be allowed before entering a USER + command. The server may use this reply to specify + site-dependent parameters, e.g., in response to HELP SITE. + + NOOP (NOOP) + + This command does not affect any parameters or previously + entered commands. It specifies no action other than that the + server send an OK reply. + + The File Transfer Protocol follows the specifications of the Telnet + protocol for all communications over the control connection. Since + the language used for Telnet communication may be a negotiated + option, all references in the next two sections will be to the + "Telnet language" and the corresponding "Telnet end-of-line code". + Currently, one may take these to mean NVT-ASCII and . No other + specifications of the Telnet protocol will be cited. + + FTP commands are "Telnet strings" terminated by the "Telnet end of + line code". The command codes themselves are alphabetic characters + terminated by the character (Space) if parameters follow and + Telnet-EOL otherwise. The command codes and the semantics of + commands are described in this section; the detailed syntax of + commands is specified in the Section on Commands, the reply sequences + are discussed in the Section on Sequencing of Commands and Replies, + and scenarios illustrating the use of commands are provided in the + Section on Typical FTP Scenarios. + + FTP commands may be partitioned as those specifying access-control + identifiers, data transfer parameters, or FTP service requests. + Certain commands (such as ABOR, STAT, QUIT) may be sent over the + control connection while a data transfer is in progress. Some + + +Postel & Reynolds [Page 34] + + + +RFC 959 October 1985 +File Transfer Protocol + + + servers may not be able to monitor the control and data connections + simultaneously, in which case some special action will be necessary + to get the server's attention. The following ordered format is + tentatively recommended: + + 1. User system inserts the Telnet "Interrupt Process" (IP) signal + in the Telnet stream. + + 2. User system sends the Telnet "Synch" signal. + + 3. User system inserts the command (e.g., ABOR) in the Telnet + stream. + + 4. Server PI, after receiving "IP", scans the Telnet stream for + EXACTLY ONE FTP command. + + (For other servers this may not be necessary but the actions listed + above should have no unusual effect.) + + 4.2. FTP REPLIES + + Replies to File Transfer Protocol commands are devised to ensure + the synchronization of requests and actions in the process of file + transfer, and to guarantee that the user process always knows the + state of the Server. Every command must generate at least one + reply, although there may be more than one; in the latter case, + the multiple replies must be easily distinguished. In addition, + some commands occur in sequential groups, such as USER, PASS and + ACCT, or RNFR and RNTO. The replies show the existence of an + intermediate state if all preceding commands have been successful. + A failure at any point in the sequence necessitates the repetition + of the entire sequence from the beginning. + + The details of the command-reply sequence are made explicit in + a set of state diagrams below. + + An FTP reply consists of a three digit number (transmitted as + three alphanumeric characters) followed by some text. The number + is intended for use by automata to determine what state to enter + next; the text is intended for the human user. It is intended + that the three digits contain enough encoded information that the + user-process (the User-PI) will not need to examine the text and + may either discard it or pass it on to the user, as appropriate. + In particular, the text may be server-dependent, so there are + likely to be varying texts for each reply code. + + A reply is defined to contain the 3-digit code, followed by Space + + +Postel & Reynolds [Page 35] + + + +RFC 959 October 1985 +File Transfer Protocol + + + , followed by one line of text (where some maximum line length + has been specified), and terminated by the Telnet end-of-line + code. There will be cases however, where the text is longer than + a single line. In these cases the complete text must be bracketed + so the User-process knows when it may stop reading the reply (i.e. + stop processing input on the control connection) and go do other + things. This requires a special format on the first line to + indicate that more than one line is coming, and another on the + last line to designate it as the last. At least one of these must + contain the appropriate reply code to indicate the state of the + transaction. To satisfy all factions, it was decided that both + the first and last line codes should be the same. + + Thus the format for multi-line replies is that the first line + will begin with the exact required reply code, followed + immediately by a Hyphen, "-" (also known as Minus), followed by + text. The last line will begin with the same code, followed + immediately by Space , optionally some text, and the Telnet + end-of-line code. + + For example: + 123-First line + Second line + 234 A line beginning with numbers + 123 The last line + + The user-process then simply needs to search for the second + occurrence of the same reply code, followed by (Space), at + the beginning of a line, and ignore all intermediary lines. If + an intermediary line begins with a 3-digit number, the Server + must pad the front to avoid confusion. + + This scheme allows standard system routines to be used for + reply information (such as for the STAT reply), with + "artificial" first and last lines tacked on. In rare cases + where these routines are able to generate three digits and a + Space at the beginning of any line, the beginning of each + text line should be offset by some neutral text, like Space. + + This scheme assumes that multi-line replies may not be nested. + + The three digits of the reply each have a special significance. + This is intended to allow a range of very simple to very + sophisticated responses by the user-process. The first digit + denotes whether the response is good, bad or incomplete. + (Referring to the state diagram), an unsophisticated user-process + will be able to determine its next action (proceed as planned, + + +Postel & Reynolds [Page 36] + + + +RFC 959 October 1985 +File Transfer Protocol + + + redo, retrench, etc.) by simply examining this first digit. A + user-process that wants to know approximately what kind of error + occurred (e.g. file system error, command syntax error) may + examine the second digit, reserving the third digit for the finest + gradation of information (e.g., RNTO command without a preceding + RNFR). + + There are five values for the first digit of the reply code: + + 1yz Positive Preliminary reply + + The requested action is being initiated; expect another + reply before proceeding with a new command. (The + user-process sending another command before the + completion reply would be in violation of protocol; but + server-FTP processes should queue any commands that + arrive while a preceding command is in progress.) This + type of reply can be used to indicate that the command + was accepted and the user-process may now pay attention + to the data connections, for implementations where + simultaneous monitoring is difficult. The server-FTP + process may send at most, one 1yz reply per command. + + 2yz Positive Completion reply + + The requested action has been successfully completed. A + new request may be initiated. + + 3yz Positive Intermediate reply + + The command has been accepted, but the requested action + is being held in abeyance, pending receipt of further + information. The user should send another command + specifying this information. This reply is used in + command sequence groups. + + 4yz Transient Negative Completion reply + + The command was not accepted and the requested action did + not take place, but the error condition is temporary and + the action may be requested again. The user should + return to the beginning of the command sequence, if any. + It is difficult to assign a meaning to "transient", + particularly when two distinct sites (Server- and + User-processes) have to agree on the interpretation. + Each reply in the 4yz category might have a slightly + different time value, but the intent is that the + + +Postel & Reynolds [Page 37] + + + +RFC 959 October 1985 +File Transfer Protocol + + + user-process is encouraged to try again. A rule of thumb + in determining if a reply fits into the 4yz or the 5yz + (Permanent Negative) category is that replies are 4yz if + the commands can be repeated without any change in + command form or in properties of the User or Server + (e.g., the command is spelled the same with the same + arguments used; the user does not change his file access + or user name; the server does not put up a new + implementation.) + + 5yz Permanent Negative Completion reply + + The command was not accepted and the requested action did + not take place. The User-process is discouraged from + repeating the exact request (in the same sequence). Even + some "permanent" error conditions can be corrected, so + the human user may want to direct his User-process to + reinitiate the command sequence by direct action at some + point in the future (e.g., after the spelling has been + changed, or the user has altered his directory status.) + + The following function groupings are encoded in the second + digit: + + x0z Syntax - These replies refer to syntax errors, + syntactically correct commands that don't fit any + functional category, unimplemented or superfluous + commands. + + x1z Information - These are replies to requests for + information, such as status or help. + + x2z Connections - Replies referring to the control and + data connections. + + x3z Authentication and accounting - Replies for the login + process and accounting procedures. + + x4z Unspecified as yet. + + x5z File system - These replies indicate the status of the + Server file system vis-a-vis the requested transfer or + other file system action. + + The third digit gives a finer gradation of meaning in each of + the function categories, specified by the second digit. The + list of replies below will illustrate this. Note that the text + + +Postel & Reynolds [Page 38] + + + +RFC 959 October 1985 +File Transfer Protocol + + + associated with each reply is recommended, rather than + mandatory, and may even change according to the command with + which it is associated. The reply codes, on the other hand, + must strictly follow the specifications in the last section; + that is, Server implementations should not invent new codes for + situations that are only slightly different from the ones + described here, but rather should adapt codes already defined. + + A command such as TYPE or ALLO whose successful execution + does not offer the user-process any new information will + cause a 200 reply to be returned. If the command is not + implemented by a particular Server-FTP process because it + has no relevance to that computer system, for example ALLO + at a TOPS20 site, a Positive Completion reply is still + desired so that the simple User-process knows it can proceed + with its course of action. A 202 reply is used in this case + with, for example, the reply text: "No storage allocation + necessary." If, on the other hand, the command requests a + non-site-specific action and is unimplemented, the response + is 502. A refinement of that is the 504 reply for a command + that is implemented, but that requests an unimplemented + parameter. + + 4.2.1 Reply Codes by Function Groups + + 200 Command okay. + 500 Syntax error, command unrecognized. + This may include errors such as command line too long. + 501 Syntax error in parameters or arguments. + 202 Command not implemented, superfluous at this site. + 502 Command not implemented. + 503 Bad sequence of commands. + 504 Command not implemented for that parameter. + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 39] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 110 Restart marker reply. + In this case, the text is exact and not left to the + particular implementation; it must read: + MARK yyyy = mmmm + Where yyyy is User-process data stream marker, and mmmm + server's equivalent marker (note the spaces between markers + and "="). + 211 System status, or system help reply. + 212 Directory status. + 213 File status. + 214 Help message. + On how to use the server or the meaning of a particular + non-standard command. This reply is useful only to the + human user. + 215 NAME system type. + Where NAME is an official system name from the list in the + Assigned Numbers document. + + 120 Service ready in nnn minutes. + 220 Service ready for new user. + 221 Service closing control connection. + Logged out if appropriate. + 421 Service not available, closing control connection. + This may be a reply to any command if the service knows it + must shut down. + 125 Data connection already open; transfer starting. + 225 Data connection open; no transfer in progress. + 425 Can't open data connection. + 226 Closing data connection. + Requested file action successful (for example, file + transfer or file abort). + 426 Connection closed; transfer aborted. + 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). + + 230 User logged in, proceed. + 530 Not logged in. + 331 User name okay, need password. + 332 Need account for login. + 532 Need account for storing files. + + + + + + + + + + +Postel & Reynolds [Page 40] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 150 File status okay; about to open data connection. + 250 Requested file action okay, completed. + 257 "PATHNAME" created. + 350 Requested file action pending further information. + 450 Requested file action not taken. + File unavailable (e.g., file busy). + 550 Requested action not taken. + File unavailable (e.g., file not found, no access). + 451 Requested action aborted. Local error in processing. + 551 Requested action aborted. Page type unknown. + 452 Requested action not taken. + Insufficient storage space in system. + 552 Requested file action aborted. + Exceeded storage allocation (for current directory or + dataset). + 553 Requested action not taken. + File name not allowed. + + + 4.2.2 Numeric Order List of Reply Codes + + 110 Restart marker reply. + In this case, the text is exact and not left to the + particular implementation; it must read: + MARK yyyy = mmmm + Where yyyy is User-process data stream marker, and mmmm + server's equivalent marker (note the spaces between markers + and "="). + 120 Service ready in nnn minutes. + 125 Data connection already open; transfer starting. + 150 File status okay; about to open data connection. + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 41] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 200 Command okay. + 202 Command not implemented, superfluous at this site. + 211 System status, or system help reply. + 212 Directory status. + 213 File status. + 214 Help message. + On how to use the server or the meaning of a particular + non-standard command. This reply is useful only to the + human user. + 215 NAME system type. + Where NAME is an official system name from the list in the + Assigned Numbers document. + 220 Service ready for new user. + 221 Service closing control connection. + Logged out if appropriate. + 225 Data connection open; no transfer in progress. + 226 Closing data connection. + Requested file action successful (for example, file + transfer or file abort). + 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). + 230 User logged in, proceed. + 250 Requested file action okay, completed. + 257 "PATHNAME" created. + + 331 User name okay, need password. + 332 Need account for login. + 350 Requested file action pending further information. + + 421 Service not available, closing control connection. + This may be a reply to any command if the service knows it + must shut down. + 425 Can't open data connection. + 426 Connection closed; transfer aborted. + 450 Requested file action not taken. + File unavailable (e.g., file busy). + 451 Requested action aborted: local error in processing. + 452 Requested action not taken. + Insufficient storage space in system. + + + + + + + + + + + +Postel & Reynolds [Page 42] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 500 Syntax error, command unrecognized. + This may include errors such as command line too long. + 501 Syntax error in parameters or arguments. + 502 Command not implemented. + 503 Bad sequence of commands. + 504 Command not implemented for that parameter. + 530 Not logged in. + 532 Need account for storing files. + 550 Requested action not taken. + File unavailable (e.g., file not found, no access). + 551 Requested action aborted: page type unknown. + 552 Requested file action aborted. + Exceeded storage allocation (for current directory or + dataset). + 553 Requested action not taken. + File name not allowed. + + +5. DECLARATIVE SPECIFICATIONS + + 5.1. MINIMUM IMPLEMENTATION + + In order to make FTP workable without needless error messages, the + following minimum implementation is required for all servers: + + TYPE - ASCII Non-print + MODE - Stream + STRUCTURE - File, Record + COMMANDS - USER, QUIT, PORT, + TYPE, MODE, STRU, + for the default values + RETR, STOR, + NOOP. + + The default values for transfer parameters are: + + TYPE - ASCII Non-print + MODE - Stream + STRU - File + + All hosts must accept the above as the standard defaults. + + + + + + + + +Postel & Reynolds [Page 43] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.2. CONNECTIONS + + The server protocol interpreter shall "listen" on Port L. The + user or user protocol interpreter shall initiate the full-duplex + control connection. Server- and user- processes should follow the + conventions of the Telnet protocol as specified in the + ARPA-Internet Protocol Handbook [1]. Servers are under no + obligation to provide for editing of command lines and may require + that it be done in the user host. The control connection shall be + closed by the server at the user's request after all transfers and + replies are completed. + + The user-DTP must "listen" on the specified data port; this may be + the default user port (U) or a port specified in the PORT command. + The server shall initiate the data connection from his own default + data port (L-1) using the specified user data port. The direction + of the transfer and the port used will be determined by the FTP + service command. + + Note that all FTP implementation must support data transfer using + the default port, and that only the USER-PI may initiate the use + of non-default ports. + + When data is to be transferred between two servers, A and B (refer + to Figure 2), the user-PI, C, sets up control connections with + both server-PI's. One of the servers, say A, is then sent a PASV + command telling him to "listen" on his data port rather than + initiate a connection when he receives a transfer service command. + When the user-PI receives an acknowledgment to the PASV command, + which includes the identity of the host and port being listened + on, the user-PI then sends A's port, a, to B in a PORT command; a + reply is returned. The user-PI may then send the corresponding + service commands to A and B. Server B initiates the connection + and the transfer proceeds. The command-reply sequence is listed + below where the messages are vertically synchronous but + horizontally asynchronous: + + + + + + + + + + + + + +Postel & Reynolds [Page 44] + + + +RFC 959 October 1985 +File Transfer Protocol + + + User-PI - Server A User-PI - Server B + ------------------ ------------------ + + C->A : Connect C->B : Connect + C->A : PASV + A->C : 227 Entering Passive Mode. A1,A2,A3,A4,a1,a2 + C->B : PORT A1,A2,A3,A4,a1,a2 + B->C : 200 Okay + C->A : STOR C->B : RETR + B->A : Connect to HOST-A, PORT-a + + Figure 3 + + The data connection shall be closed by the server under the + conditions described in the Section on Establishing Data + Connections. If the data connection is to be closed following a + data transfer where closing the connection is not required to + indicate the end-of-file, the server must do so immediately. + Waiting until after a new transfer command is not permitted + because the user-process will have already tested the data + connection to see if it needs to do a "listen"; (remember that the + user must "listen" on a closed data port BEFORE sending the + transfer request). To prevent a race condition here, the server + sends a reply (226) after closing the data connection (or if the + connection is left open, a "file transfer completed" reply (250) + and the user-PI should wait for one of these replies before + issuing a new transfer command). + + Any time either the user or server see that the connection is + being closed by the other side, it should promptly read any + remaining data queued on the connection and issue the close on its + own side. + + 5.3. COMMANDS + + The commands are Telnet character strings transmitted over the + control connections as described in the Section on FTP Commands. + The command functions and semantics are described in the Section + on Access Control Commands, Transfer Parameter Commands, FTP + Service Commands, and Miscellaneous Commands. The command syntax + is specified here. + + The commands begin with a command code followed by an argument + field. The command codes are four or fewer alphabetic characters. + Upper and lower case alphabetic characters are to be treated + identically. Thus, any of the following may represent the + retrieve command: + + +Postel & Reynolds [Page 45] + + + +RFC 959 October 1985 +File Transfer Protocol + + + RETR Retr retr ReTr rETr + + This also applies to any symbols representing parameter values, + such as A or a for ASCII TYPE. The command codes and the argument + fields are separated by one or more spaces. + + The argument field consists of a variable length character string + ending with the character sequence (Carriage Return, Line + Feed) for NVT-ASCII representation; for other negotiated languages + a different end of line character might be used. It should be + noted that the server is to take no action until the end of line + code is received. + + The syntax is specified below in NVT-ASCII. All characters in the + argument field are ASCII characters including any ASCII + represented decimal integers. Square brackets denote an optional + argument field. If the option is not taken, the appropriate + default is implied. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 46] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.3.1. FTP COMMANDS + + The following are the FTP commands: + + USER + PASS + ACCT + CWD + CDUP + SMNT + QUIT + REIN + PORT + PASV + TYPE + STRU + MODE + RETR + STOR + STOU + APPE + ALLO + [ R ] + REST + RNFR + RNTO + ABOR + DELE + RMD + MKD + PWD + LIST [ ] + NLST [ ] + SITE + SYST + STAT [ ] + HELP [ ] + NOOP + + + + + + + + + + + +Postel & Reynolds [Page 47] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.3.2. FTP COMMAND ARGUMENTS + + The syntax of the above argument fields (using BNF notation + where applicable) is: + + ::= + ::= + ::= + ::= | + ::= any of the 128 ASCII characters except and + + ::= + ::= | + ::= printable characters, any + ASCII code 33 through 126 + ::= + ::= , + ::= ,,, + ::= , + ::= any decimal integer 1 through 255 + ::= N | T | C + ::= A [ ] + | E [ ] + | I + | L + ::= F | R | P + ::= S | B | C + ::= + ::= any decimal integer + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 48] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.4. SEQUENCING OF COMMANDS AND REPLIES + + The communication between the user and server is intended to be an + alternating dialogue. As such, the user issues an FTP command and + the server responds with a prompt primary reply. The user should + wait for this initial primary success or failure response before + sending further commands. + + Certain commands require a second reply for which the user should + also wait. These replies may, for example, report on the progress + or completion of file transfer or the closing of the data + connection. They are secondary replies to file transfer commands. + + One important group of informational replies is the connection + greetings. Under normal circumstances, a server will send a 220 + reply, "awaiting input", when the connection is completed. The + user should wait for this greeting message before sending any + commands. If the server is unable to accept input right away, a + 120 "expected delay" reply should be sent immediately and a 220 + reply when ready. The user will then know not to hang up if there + is a delay. + + Spontaneous Replies + + Sometimes "the system" spontaneously has a message to be sent + to a user (usually all users). For example, "System going down + in 15 minutes". There is no provision in FTP for such + spontaneous information to be sent from the server to the user. + It is recommended that such information be queued in the + server-PI and delivered to the user-PI in the next reply + (possibly making it a multi-line reply). + + The table below lists alternative success and failure replies for + each command. These must be strictly adhered to; a server may + substitute text in the replies, but the meaning and action implied + by the code numbers and by the specific command reply sequence + cannot be altered. + + Command-Reply Sequences + + In this section, the command-reply sequence is presented. Each + command is listed with its possible replies; command groups are + listed together. Preliminary replies are listed first (with + their succeeding replies indented and under them), then + positive and negative completion, and finally intermediary + + + + +Postel & Reynolds [Page 49] + + + +RFC 959 October 1985 +File Transfer Protocol + + + replies with the remaining commands from the sequence + following. This listing forms the basis for the state + diagrams, which will be presented separately. + + Connection Establishment + 120 + 220 + 220 + 421 + Login + USER + 230 + 530 + 500, 501, 421 + 331, 332 + PASS + 230 + 202 + 530 + 500, 501, 503, 421 + 332 + ACCT + 230 + 202 + 530 + 500, 501, 503, 421 + CWD + 250 + 500, 501, 502, 421, 530, 550 + CDUP + 200 + 500, 501, 502, 421, 530, 550 + SMNT + 202, 250 + 500, 501, 502, 421, 530, 550 + Logout + REIN + 120 + 220 + 220 + 421 + 500, 502 + QUIT + 221 + 500 + + + + +Postel & Reynolds [Page 50] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Transfer parameters + PORT + 200 + 500, 501, 421, 530 + PASV + 227 + 500, 501, 502, 421, 530 + MODE + 200 + 500, 501, 504, 421, 530 + TYPE + 200 + 500, 501, 504, 421, 530 + STRU + 200 + 500, 501, 504, 421, 530 + File action commands + ALLO + 200 + 202 + 500, 501, 504, 421, 530 + REST + 500, 501, 502, 421, 530 + 350 + STOR + 125, 150 + (110) + 226, 250 + 425, 426, 451, 551, 552 + 532, 450, 452, 553 + 500, 501, 421, 530 + STOU + 125, 150 + (110) + 226, 250 + 425, 426, 451, 551, 552 + 532, 450, 452, 553 + 500, 501, 421, 530 + RETR + 125, 150 + (110) + 226, 250 + 425, 426, 451 + 450, 550 + 500, 501, 421, 530 + + + + +Postel & Reynolds [Page 51] + + + +RFC 959 October 1985 +File Transfer Protocol + + + LIST + 125, 150 + 226, 250 + 425, 426, 451 + 450 + 500, 501, 502, 421, 530 + NLST + 125, 150 + 226, 250 + 425, 426, 451 + 450 + 500, 501, 502, 421, 530 + APPE + 125, 150 + (110) + 226, 250 + 425, 426, 451, 551, 552 + 532, 450, 550, 452, 553 + 500, 501, 502, 421, 530 + RNFR + 450, 550 + 500, 501, 502, 421, 530 + 350 + RNTO + 250 + 532, 553 + 500, 501, 502, 503, 421, 530 + DELE + 250 + 450, 550 + 500, 501, 502, 421, 530 + RMD + 250 + 500, 501, 502, 421, 530, 550 + MKD + 257 + 500, 501, 502, 421, 530, 550 + PWD + 257 + 500, 501, 502, 421, 550 + ABOR + 225, 226 + 500, 501, 502, 421 + + + + + + +Postel & Reynolds [Page 52] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Informational commands + SYST + 215 + 500, 501, 502, 421 + STAT + 211, 212, 213 + 450 + 500, 501, 502, 421, 530 + HELP + 211, 214 + 500, 501, 502, 421 + Miscellaneous commands + SITE + 200 + 202 + 500, 501, 530 + NOOP + 200 + 500 421 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 53] + + + +RFC 959 October 1985 +File Transfer Protocol + + +6. STATE DIAGRAMS + + Here we present state diagrams for a very simple minded FTP + implementation. Only the first digit of the reply codes is used. + There is one state diagram for each group of FTP commands or command + sequences. + + The command groupings were determined by constructing a model for + each command then collecting together the commands with structurally + identical models. + + For each command or command sequence there are three possible + outcomes: success (S), failure (F), and error (E). In the state + diagrams below we use the symbol B for "begin", and the symbol W for + "wait for reply". + + We first present the diagram that represents the largest group of FTP + commands: + + + 1,3 +---+ + ----------->| E | + | +---+ + | + +---+ cmd +---+ 2 +---+ + | B |---------->| W |---------->| S | + +---+ +---+ +---+ + | + | 4,5 +---+ + ----------->| F | + +---+ + + + This diagram models the commands: + + ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV, + QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, and TYPE. + + + + + + + + + + + + +Postel & Reynolds [Page 54] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The other large group of commands is represented by a very similar + diagram: + + + 3 +---+ + ----------->| E | + | +---+ + | + +---+ cmd +---+ 2 +---+ + | B |---------->| W |---------->| S | + +---+ --->+---+ +---+ + | | | + | | | 4,5 +---+ + | 1 | ----------->| F | + ----- +---+ + + + This diagram models the commands: + + APPE, LIST, NLST, REIN, RETR, STOR, and STOU. + + Note that this second model could also be used to represent the first + group of commands, the only difference being that in the first group + the 100 series replies are unexpected and therefore treated as error, + while the second group expects (some may require) 100 series replies. + Remember that at most, one 100 series reply is allowed per command. + + The remaining diagrams model command sequences, perhaps the simplest + of these is the rename sequence: + + + +---+ RNFR +---+ 1,2 +---+ + | B |---------->| W |---------->| E | + +---+ +---+ -->+---+ + | | | + 3 | | 4,5 | + -------------- ------ | + | | | +---+ + | ------------->| S | + | | 1,3 | | +---+ + | 2| -------- + | | | | + V | | | + +---+ RNTO +---+ 4,5 ----->+---+ + | |---------->| W |---------->| F | + +---+ +---+ +---+ + + + +Postel & Reynolds [Page 55] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The next diagram is a simple model of the Restart command: + + + +---+ REST +---+ 1,2 +---+ + | B |---------->| W |---------->| E | + +---+ +---+ -->+---+ + | | | + 3 | | 4,5 | + -------------- ------ | + | | | +---+ + | ------------->| S | + | | 3 | | +---+ + | 2| -------- + | | | | + V | | | + +---+ cmd +---+ 4,5 ----->+---+ + | |---------->| W |---------->| F | + +---+ -->+---+ +---+ + | | + | 1 | + ------ + + + Where "cmd" is APPE, STOR, or RETR. + + We note that the above three models are similar. The Restart differs + from the Rename two only in the treatment of 100 series replies at + the second stage, while the second group expects (some may require) + 100 series replies. Remember that at most, one 100 series reply is + allowed per command. + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 56] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The most complicated diagram is for the Login sequence: + + + 1 + +---+ USER +---+------------->+---+ + | B |---------->| W | 2 ---->| E | + +---+ +---+------ | -->+---+ + | | | | | + 3 | | 4,5 | | | + -------------- ----- | | | + | | | | | + | | | | | + | --------- | + | 1| | | | + V | | | | + +---+ PASS +---+ 2 | ------>+---+ + | |---------->| W |------------->| S | + +---+ +---+ ---------->+---+ + | | | | | + 3 | |4,5| | | + -------------- -------- | + | | | | | + | | | | | + | ----------- + | 1,3| | | | + V | 2| | | + +---+ ACCT +---+-- | ----->+---+ + | |---------->| W | 4,5 -------->| F | + +---+ +---+------------->+---+ + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 57] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Finally, we present a generalized diagram that could be used to model + the command and reply interchange: + + + ------------------------------------ + | | + Begin | | + | V | + | +---+ cmd +---+ 2 +---+ | + -->| |------->| |---------->| | | + | | | W | | S |-----| + -->| | -->| |----- | | | + | +---+ | +---+ 4,5 | +---+ | + | | | | | | | + | | | 1| |3 | +---+ | + | | | | | | | | | + | | ---- | ---->| F |----- + | | | | | + | | | +---+ + ------------------- + | + | + V + End + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 58] + + + +RFC 959 October 1985 +File Transfer Protocol + + +7. TYPICAL FTP SCENARIO + + User at host U wanting to transfer files to/from host S: + + In general, the user will communicate to the server via a mediating + user-FTP process. The following may be a typical scenario. The + user-FTP prompts are shown in parentheses, '---->' represents + commands from host U to host S, and '<----' represents replies from + host S to host U. + + LOCAL COMMANDS BY USER ACTION INVOLVED + + ftp (host) multics Connect to host S, port L, + establishing control connections. + <---- 220 Service ready . + username Doe USER Doe----> + <---- 331 User name ok, + need password. + password mumble PASS mumble----> + <---- 230 User logged in. + retrieve (local type) ASCII + (local pathname) test 1 User-FTP opens local file in ASCII. + (for. pathname) test.pl1 RETR test.pl1 ----> + <---- 150 File status okay; + about to open data + connection. + Server makes data connection + to port U. + + <---- 226 Closing data connection, + file transfer successful. + type Image TYPE I ----> + <---- 200 Command OK + store (local type) image + (local pathname) file dump User-FTP opens local file in Image. + (for.pathname) >udd>cn>fd STOR >udd>cn>fd ----> + <---- 550 Access denied + terminate QUIT ----> + Server closes all + connections. + +8. CONNECTION ESTABLISHMENT + + The FTP control connection is established via TCP between the user + process port U and the server process port L. This protocol is + assigned the service port 21 (25 octal), that is L=21. + + + +Postel & Reynolds [Page 59] + + + +RFC 959 October 1985 +File Transfer Protocol + + +APPENDIX I - PAGE STRUCTURE + + The need for FTP to support page structure derives principally from + the need to support efficient transmission of files between TOPS-20 + systems, particularly the files used by NLS. + + The file system of TOPS-20 is based on the concept of pages. The + operating system is most efficient at manipulating files as pages. + The operating system provides an interface to the file system so that + many applications view files as sequential streams of characters. + However, a few applications use the underlying page structures + directly, and some of these create holey files. + + A TOPS-20 disk file consists of four things: a pathname, a page + table, a (possibly empty) set of pages, and a set of attributes. + + The pathname is specified in the RETR or STOR command. It includes + the directory name, file name, file name extension, and generation + number. + + The page table contains up to 2**18 entries. Each entry may be + EMPTY, or may point to a page. If it is not empty, there are also + some page-specific access bits; not all pages of a file need have the + same access protection. + + A page is a contiguous set of 512 words of 36 bits each. + + The attributes of the file, in the File Descriptor Block (FDB), + contain such things as creation time, write time, read time, writer's + byte-size, end-of-file pointer, count of reads and writes, backup + system tape numbers, etc. + + Note that there is NO requirement that entries in the page table be + contiguous. There may be empty page table slots between occupied + ones. Also, the end of file pointer is simply a number. There is no + requirement that it in fact point at the "last" datum in the file. + Ordinary sequential I/O calls in TOPS-20 will cause the end of file + pointer to be left after the last datum written, but other operations + may cause it not to be so, if a particular programming system so + requires. + + In fact, in both of these special cases, "holey" files and + end-of-file pointers NOT at the end of the file, occur with NLS data + files. + + + + + +Postel & Reynolds [Page 60] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The TOPS-20 paged files can be sent with the FTP transfer parameters: + TYPE L 36, STRU P, and MODE S (in fact, any mode could be used). + + Each page of information has a header. Each header field, which is a + logical byte, is a TOPS-20 word, since the TYPE is L 36. + + The header fields are: + + Word 0: Header Length. + + The header length is 5. + + Word 1: Page Index. + + If the data is a disk file page, this is the number of that + page in the file's page map. Empty pages (holes) in the file + are simply not sent. Note that a hole is NOT the same as a + page of zeros. + + Word 2: Data Length. + + The number of data words in this page, following the header. + Thus, the total length of the transmission unit is the Header + Length plus the Data Length. + + Word 3: Page Type. + + A code for what type of chunk this is. A data page is type 3, + the FDB page is type 2. + + Word 4: Page Access Control. + + The access bits associated with the page in the file's page + map. (This full word quantity is put into AC2 of an SPACS by + the program reading from net to disk.) + + After the header are Data Length data words. Data Length is + currently either 512 for a data page or 31 for an FDB. Trailing + zeros in a disk file page may be discarded, making Data Length less + than 512 in that case. + + + + + + + + + +Postel & Reynolds [Page 61] + + + +RFC 959 October 1985 +File Transfer Protocol + + +APPENDIX II - DIRECTORY COMMANDS + + Since UNIX has a tree-like directory structure in which directories + are as easy to manipulate as ordinary files, it is useful to expand + the FTP servers on these machines to include commands which deal with + the creation of directories. Since there are other hosts on the + ARPA-Internet which have tree-like directories (including TOPS-20 and + Multics), these commands are as general as possible. + + Four directory commands have been added to FTP: + + MKD pathname + + Make a directory with the name "pathname". + + RMD pathname + + Remove the directory with the name "pathname". + + PWD + + Print the current working directory name. + + CDUP + + Change to the parent of the current working directory. + + The "pathname" argument should be created (removed) as a + subdirectory of the current working directory, unless the "pathname" + string contains sufficient information to specify otherwise to the + server, e.g., "pathname" is an absolute pathname (in UNIX and + Multics), or pathname is something like "" to + TOPS-20. + + REPLY CODES + + The CDUP command is a special case of CWD, and is included to + simplify the implementation of programs for transferring directory + trees between operating systems having different syntaxes for + naming the parent directory. The reply codes for CDUP be + identical to the reply codes of CWD. + + The reply codes for RMD be identical to the reply codes for its + file analogue, DELE. + + The reply codes for MKD, however, are a bit more complicated. A + freshly created directory will probably be the object of a future + + +Postel & Reynolds [Page 62] + + + +RFC 959 October 1985 +File Transfer Protocol + + + CWD command. Unfortunately, the argument to MKD may not always be + a suitable argument for CWD. This is the case, for example, when + a TOPS-20 subdirectory is created by giving just the subdirectory + name. That is, with a TOPS-20 server FTP, the command sequence + + MKD MYDIR + CWD MYDIR + + will fail. The new directory may only be referred to by its + "absolute" name; e.g., if the MKD command above were issued while + connected to the directory , the new subdirectory + could only be referred to by the name . + + Even on UNIX and Multics, however, the argument given to MKD may + not be suitable. If it is a "relative" pathname (i.e., a pathname + which is interpreted relative to the current directory), the user + would need to be in the same current directory in order to reach + the subdirectory. Depending on the application, this may be + inconvenient. It is not very robust in any case. + + To solve these problems, upon successful completion of an MKD + command, the server should return a line of the form: + + 257"" + + That is, the server will tell the user what string to use when + referring to the created directory. The directory name can + contain any character; embedded double-quotes should be escaped by + double-quotes (the "quote-doubling" convention). + + For example, a user connects to the directory /usr/dm, and creates + a subdirectory, named pathname: + + CWD /usr/dm + 200 directory changed to /usr/dm + MKD pathname + 257 "/usr/dm/pathname" directory created + + An example with an embedded double quote: + + MKD foo"bar + 257 "/usr/dm/foo""bar" directory created + CWD /usr/dm/foo"bar + 200 directory changed to /usr/dm/foo"bar + + + + + +Postel & Reynolds [Page 63] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The prior existence of a subdirectory with the same name is an + error, and the server must return an "access denied" error reply + in that case. + + CWD /usr/dm + 200 directory changed to /usr/dm + MKD pathname + 521-"/usr/dm/pathname" directory already exists; + 521 taking no action. + + The failure replies for MKD are analogous to its file creating + cousin, STOR. Also, an "access denied" return is given if a file + name with the same name as the subdirectory will conflict with the + creation of the subdirectory (this is a problem on UNIX, but + shouldn't be one on TOPS-20). + + Essentially because the PWD command returns the same type of + information as the successful MKD command, the successful PWD + command uses the 257 reply code as well. + + SUBTLETIES + + Because these commands will be most useful in transferring + subtrees from one machine to another, carefully observe that the + argument to MKD is to be interpreted as a sub-directory of the + current working directory, unless it contains enough information + for the destination host to tell otherwise. A hypothetical + example of its use in the TOPS-20 world: + + CWD + 200 Working directory changed + MKD overrainbow + 257 "" directory created + CWD overrainbow + 431 No such directory + CWD + 200 Working directory changed + + CWD + 200 Working directory changed to + MKD + 257 "" directory created + CWD + + Note that the first example results in a subdirectory of the + connected directory. In contrast, the argument in the second + example contains enough information for TOPS-20 to tell that the + + +Postel & Reynolds [Page 64] + + + +RFC 959 October 1985 +File Transfer Protocol + + + directory is a top-level directory. Note also that + in the first example the user "violated" the protocol by + attempting to access the freshly created directory with a name + other than the one returned by TOPS-20. Problems could have + resulted in this case had there been an directory; + this is an ambiguity inherent in some TOPS-20 implementations. + Similar considerations apply to the RMD command. The point is + this: except where to do so would violate a host's conventions for + denoting relative versus absolute pathnames, the host should treat + the operands of the MKD and RMD commands as subdirectories. The + 257 reply to the MKD command must always contain the absolute + pathname of the created directory. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 65] + + + +RFC 959 October 1985 +File Transfer Protocol + + +APPENDIX III - RFCs on FTP + + Bhushan, Abhay, "A File Transfer Protocol", RFC 114 (NIC 5823), + MIT-Project MAC, 16 April 1971. + + Harslem, Eric, and John Heafner, "Comments on RFC 114 (A File + Transfer Protocol)", RFC 141 (NIC 6726), RAND, 29 April 1971. + + Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 172 + (NIC 6794), MIT-Project MAC, 23 June 1971. + + Braden, Bob, "Comments on DTP and FTP Proposals", RFC 238 (NIC 7663), + UCLA/CCN, 29 September 1971. + + Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 265 + (NIC 7813), MIT-Project MAC, 17 November 1971. + + McKenzie, Alex, "A Suggested Addition to File Transfer Protocol", + RFC 281 (NIC 8163), BBN, 8 December 1971. + + Bhushan, Abhay, "The Use of "Set Data Type" Transaction in File + Transfer Protocol", RFC 294 (NIC 8304), MIT-Project MAC, + 25 January 1972. + + Bhushan, Abhay, "The File Transfer Protocol", RFC 354 (NIC 10596), + MIT-Project MAC, 8 July 1972. + + Bhushan, Abhay, "Comments on the File Transfer Protocol (RFC 354)", + RFC 385 (NIC 11357), MIT-Project MAC, 18 August 1972. + + Hicks, Greg, "User FTP Documentation", RFC 412 (NIC 12404), Utah, + 27 November 1972. + + Bhushan, Abhay, "File Transfer Protocol (FTP) Status and Further + Comments", RFC 414 (NIC 12406), MIT-Project MAC, 20 November 1972. + + Braden, Bob, "Comments on File Transfer Protocol", RFC 430 + (NIC 13299), UCLA/CCN, 7 February 1973. + + Thomas, Bob, and Bob Clements, "FTP Server-Server Interaction", + RFC 438 (NIC 13770), BBN, 15 January 1973. + + Braden, Bob, "Print Files in FTP", RFC 448 (NIC 13299), UCLA/CCN, + 27 February 1973. + + McKenzie, Alex, "File Transfer Protocol", RFC 454 (NIC 14333), BBN, + 16 February 1973. + + +Postel & Reynolds [Page 66] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Bressler, Bob, and Bob Thomas, "Mail Retrieval via FTP", RFC 458 + (NIC 14378), BBN-NET and BBN-TENEX, 20 February 1973. + + Neigus, Nancy, "File Transfer Protocol", RFC 542 (NIC 17759), BBN, + 12 July 1973. + + Krilanovich, Mark, and George Gregg, "Comments on the File Transfer + Protocol", RFC 607 (NIC 21255), UCSB, 7 January 1974. + + Pogran, Ken, and Nancy Neigus, "Response to RFC 607 - Comments on the + File Transfer Protocol", RFC 614 (NIC 21530), BBN, 28 January 1974. + + Krilanovich, Mark, George Gregg, Wayne Hathaway, and Jim White, + "Comments on the File Transfer Protocol", RFC 624 (NIC 22054), UCSB, + Ames Research Center, SRI-ARC, 28 February 1974. + + Bhushan, Abhay, "FTP Comments and Response to RFC 430", RFC 463 + (NIC 14573), MIT-DMCG, 21 February 1973. + + Braden, Bob, "FTP Data Compression", RFC 468 (NIC 14742), UCLA/CCN, + 8 March 1973. + + Bhushan, Abhay, "FTP and Network Mail System", RFC 475 (NIC 14919), + MIT-DMCG, 6 March 1973. + + Bressler, Bob, and Bob Thomas "FTP Server-Server Interaction - II", + RFC 478 (NIC 14947), BBN-NET and BBN-TENEX, 26 March 1973. + + White, Jim, "Use of FTP by the NIC Journal", RFC 479 (NIC 14948), + SRI-ARC, 8 March 1973. + + White, Jim, "Host-Dependent FTP Parameters", RFC 480 (NIC 14949), + SRI-ARC, 8 March 1973. + + Padlipsky, Mike, "An FTP Command-Naming Problem", RFC 506 + (NIC 16157), MIT-Multics, 26 June 1973. + + Day, John, "Memo to FTP Group (Proposal for File Access Protocol)", + RFC 520 (NIC 16819), Illinois, 25 June 1973. + + Merryman, Robert, "The UCSD-CC Server-FTP Facility", RFC 532 + (NIC 17451), UCSD-CC, 22 June 1973. + + Braden, Bob, "TENEX FTP Problem", RFC 571 (NIC 18974), UCLA/CCN, + 15 November 1973. + + + + +Postel & Reynolds [Page 67] + + + +RFC 959 October 1985 +File Transfer Protocol + + + McKenzie, Alex, and Jon Postel, "Telnet and FTP Implementation - + Schedule Change", RFC 593 (NIC 20615), BBN and MITRE, + 29 November 1973. + + Sussman, Julie, "FTP Error Code Usage for More Reliable Mail + Service", RFC 630 (NIC 30237), BBN, 10 April 1974. + + Postel, Jon, "Revised FTP Reply Codes", RFC 640 (NIC 30843), + UCLA/NMC, 5 June 1974. + + Harvey, Brian, "Leaving Well Enough Alone", RFC 686 (NIC 32481), + SU-AI, 10 May 1975. + + Harvey, Brian, "One More Try on the FTP", RFC 691 (NIC 32700), SU-AI, + 28 May 1975. + + Lieb, J., "CWD Command of FTP", RFC 697 (NIC 32963), 14 July 1975. + + Harrenstien, Ken, "FTP Extension: XSEN", RFC 737 (NIC 42217), SRI-KL, + 31 October 1977. + + Harrenstien, Ken, "FTP Extension: XRSQ/XRCP", RFC 743 (NIC 42758), + SRI-KL, 30 December 1977. + + Lebling, P. David, "Survey of FTP Mail and MLFL", RFC 751, MIT, + 10 December 1978. + + Postel, Jon, "File Transfer Protocol Specification", RFC 765, ISI, + June 1980. + + Mankins, David, Dan Franklin, and Buzz Owen, "Directory Oriented FTP + Commands", RFC 776, BBN, December 1980. + + Padlipsky, Michael, "FTP Unique-Named Store Command", RFC 949, MITRE, + July 1985. + + + + + + + + + + + + + + +Postel & Reynolds [Page 68] + + + +RFC 959 October 1985 +File Transfer Protocol + + +REFERENCES + + [1] Feinler, Elizabeth, "Internet Protocol Transition Workbook", + Network Information Center, SRI International, March 1982. + + [2] Postel, Jon, "Transmission Control Protocol - DARPA Internet + Program Protocol Specification", RFC 793, DARPA, September 1981. + + [3] Postel, Jon, and Joyce Reynolds, "Telnet Protocol + Specification", RFC 854, ISI, May 1983. + + [4] Reynolds, Joyce, and Jon Postel, "Assigned Numbers", RFC 943, + ISI, April 1985. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 69] + diff --git a/lib/ftp/doc/html/.gitignore b/lib/ftp/doc/html/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ftp/doc/man3/.gitignore b/lib/ftp/doc/man3/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ftp/doc/man6/.gitignore b/lib/ftp/doc/man6/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ftp/doc/pdf/.gitignore b/lib/ftp/doc/pdf/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ftp/doc/src/Makefile b/lib/ftp/doc/src/Makefile new file mode 100644 index 0000000000..01ea2f970b --- /dev/null +++ b/lib/ftp/doc/src/Makefile @@ -0,0 +1,154 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2018. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% +# +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(FTP_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +XML_APPLICATION_FILES = ref_man.xml + +XML_CHAPTER_FILES = \ + introduction.xml \ + ftp_client.xml \ + notes.xml + +XML_REF3_FILES = \ + ftp.xml + +XML_PART_FILES = \ + part.xml + +BOOK_FILES = book.xml + +XML_FILES = \ + $(BOOK_FILES) \ + $(XML_CHAPTER_FILES) \ + $(XML_PART_FILES) \ + $(XML_REF6_FILES) \ + $(XML_REF3_FILES) \ + $(XML_APPLICATION_FILES) + +# GIF_FILES = inets.gif + + +# ---------------------------------------------------- + +HTML_FILES = \ + $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) + +INFO_FILE = ../../info +EXTRA_FILES = \ + $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) + +MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) + +HTML_REF_MAN_FILE = $(HTMLDIR)/index.html + +TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +XML_FLAGS += +DVIPS_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +$(HTMLDIR)/%.gif: %.gif + $(INSTALL_DATA) $< $@ + +docs: pdf html man + +ldocs: local_docs + +$(TOP_PDF_FILE): $(XML_FILES) + +pdf: $(TOP_PDF_FILE) + +html: gifs $(HTML_REF_MAN_FILE) + +clean clean_docs: clean_html clean_man clean_pdf + rm -f errs core *~ + +man: $(MAN3_FILES) + +gifs: $(GIF_FILES:%=$(HTMLDIR)/%) + +debug opt: + +clean_pdf: + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + +clean_html: + rm -rf $(TOP_HTML_FILES) $(HTMLDIR)/* + +clean_man: + rm -f $(MAN3_FILES) + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_docs_spec: docs + $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf" + $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" + $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" + $(INSTALL_DATA) $(HTMLDIR)/* "$(RELSYSDIR)/doc/html" + $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" + $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" + $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" + +release_spec: + +info: + @echo "GIF_FILES:\n$(GIF_FILES)" + @echo "" + @echo "EXTRA_FILES:\n$(EXTRA_FILES)" + @echo "" + @echo "HTML_FILES:\n$(HTML_FILES)" + @echo "" + @echo "TOP_HTML_FILES:\n$(TOP_HTML_FILES)" + @echo "" + @echo "XML_REF3_FILES:\n$(XML_REF3_FILES)" + @echo "" + @echo "XML_REF6_FILES:\n$(XML_REF6_FILES)" + @echo "" + @echo "XML_CHAPTER_FILES:\n$(XML_CHAPTER_FILES)" + @echo "" diff --git a/lib/ftp/doc/src/book.xml b/lib/ftp/doc/src/book.xml new file mode 100644 index 0000000000..1268af64bf --- /dev/null +++ b/lib/ftp/doc/src/book.xml @@ -0,0 +1,49 @@ + + + + +
+ + 19972018 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + FTP + Péter Dimitrov + + 2018-02-26 + 1.0 + book.sgml +
+ + + FTP + + + + + + + + + + + + + + +
diff --git a/lib/ftp/doc/src/ftp.xml b/lib/ftp/doc/src/ftp.xml new file mode 100644 index 0000000000..42bece4d38 --- /dev/null +++ b/lib/ftp/doc/src/ftp.xml @@ -0,0 +1,948 @@ + + + + +
+ + 19972016 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + ftp + Peter Högfeldt + + 1997-11-05 + B + ftp.xml +
+ ftp + A File Transfer Protocol client. + + + +

This module implements a client for file transfer + according to a subset of the File Transfer Protocol (FTP), see + RFC 959.

+ +

As from Inets 4.4.1, the FTP + client always tries to use passive FTP mode and only resort + to active FTP mode if this fails. This default behavior can be + changed by start option mode.

+ + + +

An FTP client can be started in two ways. One is using the + Inets service framework, + the other is to start it directly as a standalone process + using function open.

+ +

For a simple example of an FTP session, see + Inets User's Guide.

+ +

In addition to the ordinary functions for receiving and sending + files (see recv/2, recv/3, send/2, and + send/3) there are functions for receiving remote files as + binaries (see recv_bin/2) and for sending binaries to be + stored as remote files (see send_bin/3).

+ +

A set of functions is provvided for sending and receiving + contiguous parts of a file to be stored in a remote file. For send, + see send_chunk_start/2, send_chunk/2, and + send_chunk_end/1. For receive, see + recv_chunk_start/2 and recv_chunk/).

+ +

The return values of the following functions depend + much on the implementation of the FTP server at the remote + host. In particular, the results from ls and nlist + varies. Often real errors are not reported as errors by ls, + even if, for example, a file or directory does not + exist. nlist is usually more strict, but some + implementations have the peculiar behaviour of responding with an + error if the request is a listing of the contents of a directory + that exists but is empty.

+ + +
+ +
+ FTP CLIENT SERVICE START/STOP + +

The FTP client can be started and stopped dynamically in runtime by + calling the Inets application API + inets:start(ftpc, ServiceConfig), + or inets:start(ftpc, ServiceConfig, How), and + inets:stop(ftpc, Pid). + For details, see inets(3).

+ +

The available configuration options are as follows:

+ + + {host, Host} + + +

Host = string() | ip_address()

+
+ + {port, Port} + + +

Port = integer() > 0

+

Default is 21.

+
+ + {mode, Mode} + + +

Mode = active | passive

+

Default is passive.

+
+ + {verbose, Verbose} + + +

Verbose = boolean()

+

Determines if the FTP communication is to be + verbose or not.

+

Default is false.

+
+ + {debug, Debug} + + +

Debug = trace | debug | disable

+

Debugging using the dbg toolkit.

+

Default is disable.

+
+ + {ipfamily, IpFamily} + + +

IpFamily = inet | inet6 | inet6fb4

+

With inet6fb4 the client behaves as before, that is, + tries to use IPv6, and only if that does not work it + uses IPv4).

+

Default is inet (IPv4).

+
+ + {timeout, Timeout} + + +

Timeout = non_neg_integer()

+

Connection time-out.

+

Default is 60000 (milliseconds).

+
+ + {dtimeout, DTimeout} + + +

DTimeout = non_neg_integer() | infinity

+

Data connect time-out. + The time the client waits for the server to connect to the + data socket.

+

Default is infinity.

+
+ + {progress, Progress} + + +

Progress = ignore | {CBModule, CBFunction, InitProgress}

+

CBModule = atom(), CBFunction = atom()

+

InitProgress = term()

+

Default is ignore.

+
+ +
+ +

Option progress is intended to be used by applications that + want to create some type of progress report, such as a progress bar in + a GUI. Default for the progress option is ignore, + that is, the option is not used. When the progress option is + specified, the following happens when ftp:send/[3,4] or + ftp:recv/[3,4] are called:

+ + + +

Before a file is transferred, the following call is + made to indicate the start of the file transfer and how large + the file is. The return value of the callback function + is to be a new value for the UserProgressTerm that will + be used as input the next time the callback function is + called.

+

+ CBModule:CBFunction(InitProgress, File, {file_size, FileSize}) +

+
+ + +

Every time a chunk of bytes is transferred the + following call is made:

+

+ CBModule:CBFunction(UserProgressTerm, File, {transfer_size, TransferSize}) +

+
+ + +

At the end of the file the following call is + made to indicate the end of the transfer:

+

+ CBModule:CBFunction(UserProgressTerm, File, {transfer_size, 0}) +

+
+
+ +

The callback function is to be defined as follows:

+ +

+ CBModule:CBFunction(UserProgressTerm, File, Size) -> UserProgressTerm +

+ +

+ CBModule = CBFunction = atom() +

+ +

+ UserProgressTerm = term() +

+ +

+ File = string() +

+ +

+ Size = {transfer_size, integer()} | {file_size, integer()} | {file_size, unknown} +

+ +

For remote files, ftp cannot determine the + file size in a platform independent way. In this case the size + becomes unknown and it is left to the application to + determine the size.

+ + +

The callback is made by a middleman process, hence the + file transfer is not affected by the code in the progress + callback function. If the callback crashes, this is + detected by the FTP connection process, which then prints an + info-report and goes on as if the progress option was set + to ignore.

+
+ +

The file transfer type is set to the default of the FTP server + when the session is opened. This is usually ASCCI mode. +

+ +

The current local working directory (compare lpwd/1) is set + to the value reported by file:get_cwd/1, the wanted + local directory. +

+ +

The return value Pid is used as a reference to the + newly created FTP client in all other functions, and they are to + be called by the process that created the connection. The FTP + client process monitors the process that created it and + terminates if that process terminates.

+
+ +
+ DATA TYPES +

The following type definitions are used by more than one + function in the FTP client API:

+

pid() = identifier of an FTP connection

+

string() = list of ASCII characters

+

shortage_reason() = etnospc | epnospc

+

restriction_reason() = epath | efnamena | elogin | enotbinary + - all restrictions are not always relevant to all functions +

+

common_reason() = econn | eclosed | term() + - some explanation of what went wrong

+ + +
+ + + + account(Pid, Account) -> ok | {error, Reason} + Specifies which account to use. + + Pid = pid() + Account = string() + Reason = eacct | common_reason() + + +

Sets the account for an operation, if needed.

+ + + + +
+
+ + + append(Pid, LocalFile) -> + append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason} + Transfers a file to remote server, and appends it to + Remotefile. + + Pid = pid() + LocalFile = RemoteFile = string() + Reason = epath | elogin | etnospc | epnospc | efnamena | common_reason + + +

Transfers the file LocalFile to the remote server. If + RemoteFile is specified, the name of the remote file that the + file is appended to is set to RemoteFile, otherwise + to LocalFile. If the file does not exists, + it is created.

+ + +
+
+ + + append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason} + Transfers a binary into a remote file. + + Pid = pid() + Bin = binary()() + RemoteFile = string() + Reason = restriction_reason()| shortage_reason() | common_reason() + + +

Transfers the binary Bin to the remote server and appends + it to the file RemoteFile. If the file does not exist, it + is created.

+ + +
+
+ + + append_chunk(Pid, Bin) -> ok | {error, Reason} + Appends a chunk to the remote file. + + Pid = pid() + Bin = binary() + Reason = echunk | restriction_reason() | common_reason() + + +

Transfers the chunk Bin to the remote server, which + appends it to the file specified in the call to + append_chunk_start/2.

+

For some errors, for example, file system full, it is + necessary to call append_chunk_end to get the + proper reason.

+ + +
+
+ + + append_chunk_start(Pid, File) -> ok | {error, Reason} + Starts transfer of file chunks for appending to File. + + Pid = pid() + File = string() + Reason = restriction_reason() | common_reason() + + +

Starts the transfer of chunks for appending to the file + File at the remote server. If the file does not exist, + it is created.

+ + +
+
+ + + append_chunk_end(Pid) -> ok | {error, Reason} + Stops transfer of chunks for appending. + + Pid = pid() + Reason = echunk | restriction_reason() | shortage_reason() + + +

Stops transfer of chunks for appending to the remote server. + The file at the remote server, specified in the call to + append_chunk_start/2, is closed by the server.

+ + +
+
+ + + cd(Pid, Dir) -> ok | {error, Reason} + Changes remote working directory. + + Pid = pid() + Dir = string() + Reason = restriction_reason() | common_reason() + + +

Changes the working directory at the remote server to + Dir.

+ + +
+
+ + + close(Pid) -> ok + Ends the FTP session. + + Pid = pid() + + +

Ends an FTP session, created using function + open.

+ + +
+
+ + + delete(Pid, File) -> ok | {error, Reason} + Deletes a file at the remote server. + + Pid = pid() + File = string() + Reason = restriction_reason() | common_reason() + + +

Deletes the file File at the remote server.

+ + +
+
+ + + formaterror(Tag) -> string() + Returns error diagnostics. + + Tag = {error, atom()} | atom() + + +

Given an error return value {error, AtomReason}, + this function returns a readable string describing the error.

+ + +
+
+ + + lcd(Pid, Dir) -> ok | {error, Reason} + Changes local working directory. + + Pid = pid() + Dir = string() + Reason = restriction_reason() + + +

Changes the working directory to Dir for the local client.

+ + +
+
+ + + lpwd(Pid) -> {ok, Dir} + Gets local current working directory. + + Pid = pid() + + +

Returns the current working directory at the local client.

+ + + + +
+
+ + + ls(Pid) -> + ls(Pid, Pathname) -> {ok, Listing} | {error, Reason} + List of files. + + Pid = pid() + Pathname = string() + Listing = string() + Reason = restriction_reason() | common_reason() + + +

Returns a list of files in long format.

+

Pathname can be a directory, a group of files, or + a file. The Pathname string can contain wildcards.

+

ls/1 implies the current remote directory of the user.

+

The format of Listing depends on the operating system. + On UNIX, it is typically produced from the output of the + ls -l shell command.

+ + +
+
+ + + mkdir(Pid, Dir) -> ok | {error, Reason} + Creates a remote directory. + + Pid = pid() + Dir = string() + Reason = restriction_reason() | common_reason() + + +

Creates the directory Dir at the remote server.

+ + + + +
+
+ + + nlist(Pid) -> + nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason} + List of files. + + Pid = pid() + Pathname = string() + Listing = string() + Reason = restriction_reason() | common_reason() + + +

Returns a list of files in short format.

+

Pathname can be a directory, a group of files, or + a file. The Pathname string can contain wildcards.

+

nlist/1 implies the current remote directory of the user.

+

The format of Listing is a stream of + filenames where each filename is separated by <CRLF> or + <NL>. Contrary to function ls, the purpose of + nlist is to enable a program to + process filename information automatically.

+ + +
+
+ + + open(Host) -> {ok, Pid} | {error, Reason} + open(Host, Opts) -> {ok, Pid} | {error, Reason} + Starts a standalone FTP client. + + Host = string() | ip_address() + Opts = options() + options() = [option()] + option() = start_option() | open_option() + start_option() = {verbose, verbose()} | {debug, debug()} + verbose() = boolean() (default is false) + debug() = disable | debug | trace (default is disable) + open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress()} + ipfamily() = inet | inet6 | inet6fb4 (default is inet) + port() = integer() > 0 (default is 21) + mode() = active | passive (default is passive) + tls_options() = [ssl:ssloption()] + timeout() = integer() > 0 (default is 60000 milliseconds) + dtimeout() = integer() > 0 | infinity (default is infinity) + pogress() = ignore | {module(), function(), initial_data()} (default is ignore) + module() = atom() + function() = atom() + initial_data() = term() + Reason = ehost | term() + + + +

Starts a standalone FTP client process + (without the Inets service framework) and + opens a session with the FTP server at Host.

+ +

If option {tls, tls_options()} is present, the FTP session + is transported over tls (ftps, see + RFC 4217). + The list tls_options() can be empty. The function + ssl:connect/3 + is used for securing both the control connection and the data sessions. +

+ +

A session opened in this way is closed using function + close.

+ + +
+
+ + + pwd(Pid) -> {ok, Dir} | {error, Reason} + Gets the remote current working directory. + + Pid = pid() + Reason = restriction_reason() | common_reason() + + +

Returns the current working directory at the remote server.

+ + + + +
+
+ + + recv(Pid, RemoteFile) -> + recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason} + Transfers a file from remote server. + + Pid = pid() + RemoteFile = LocalFile = string() + Reason = restriction_reason() | common_reason() | file_write_error_reason() + file_write_error_reason() = see file:write/2 + + +

Transfers the file RemoteFile from the remote server + to the file system of the local client. If + LocalFile is specified, the local file will be + LocalFile, otherwise + RemoteFile.

+

If the file write fails (for example, enospc), the command is + aborted and {error, file_write_error_reason()} is returned. + However, the file is not removed.

+ + +
+
+ + + recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason} + Transfers a file from remote server as a binary. + + Pid = pid() + Bin = binary() + RemoteFile = string() + Reason = restriction_reason() | common_reason() + + +

Transfers the file RemoteFile from the remote server and + receives it as a binary.

+ + +
+
+ + + recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason} + Starts chunk-reading of the remote file. + + Pid = pid() + RemoteFile = string() + Reason = restriction_reason() | common_reason() + + +

Starts transfer of the file RemoteFile from the + remote server.

+ + +
+
+ + + recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason} + Receives a chunk of the remote file. + + Pid = pid() + Bin = binary() + Reason = restriction_reason() | common_reason() + + +

Receives a chunk of the remote file (RemoteFile of + recv_chunk_start). The return values have the following + meaning:

+ + ok = the transfer is complete. + {ok, Bin} = just another chunk of the file. + {error, Reason} = transfer failed. + + + +
+
+ + + rename(Pid, Old, New) -> ok | {error, Reason} + Renames a file at the remote server. + + Pid = pid() + CurrFile = NewFile = string() + Reason = restriction_reason() | common_reason() + + +

Renames Old to New at the remote server.

+ + +
+
+ + + rmdir(Pid, Dir) -> ok | {error, Reason} + Removes a remote directory. + + Pid = pid() + Dir = string() + Reason = restriction_reason() | common_reason() + + +

Removes directory Dir at the remote server.

+ + + + +
+
+ + + send(Pid, LocalFile) -> + send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason} + Transfers a file to the remote server. + + Pid = pid() + LocalFile = RemoteFile = string() + Reason = restriction_reason() | common_reason() | shortage_reason() + + +

Transfers the file LocalFile to the remote server. If + RemoteFile is specified, the name of the remote file is set + to RemoteFile, otherwise to LocalFile.

+ + +
+
+ + + send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason} + Transfers a binary into a remote file. + + Pid = pid() + Bin = binary()() + RemoteFile = string() + Reason = restriction_reason() | common_reason() | shortage_reason() + + +

Transfers the binary Bin into the file RemoteFile + at the remote server.

+ + +
+
+ + + send_chunk(Pid, Bin) -> ok | {error, Reason} + Writes a chunk to the remote file. + + Pid = pid() + Bin = binary() + Reason = echunk | restriction_reason() | common_reason() + + +

Transfers the chunk Bin to the remote server, which + writes it into the file specified in the call to + send_chunk_start/2.

+

For some errors, for example, file system full, it is + necessary to to call send_chunk_end to get the + proper reason.

+ + +
+
+ + + send_chunk_start(Pid, File) -> ok | {error, Reason} + Starts transfer of file chunks. + + Pid = pid() + File = string() + Reason = restriction_reason() | common_reason() + + +

Starts transfer of chunks into the file File at the + remote server.

+ + +
+
+ + + send_chunk_end(Pid) -> ok | {error, Reason} + Stops transfer of chunks. + + Pid = pid() + Reason = restriction_reason() | common_reason() | shortage_reason() + + +

Stops transfer of chunks to the remote server. The file at the + remote server, specified in the call to send_chunk_start/2 + is closed by the server.

+ + +
+
+ + + type(Pid, Type) -> ok | {error, Reason} + Sets transfer type to asciior binary. + + Pid = pid() + Type = ascii | binary + Reason = etype | restriction_reason() | common_reason() + + +

Sets the file transfer type to ascii or binary. When + an FTP session is opened, the default transfer type of the + server is used, most often ascii, which is default + according to RFC 959.

+ +
+
+ + + user(Pid, User, Password) -> ok | {error, Reason} + User login. + + Pid = pid() + User = Password = string() + Reason = euser | common_reason() + + +

Performs login of User with Password.

+ + +
+
+ + + user(Pid, User, Password, Account) -> ok | {error, Reason} + User login. + + Pid = pid() + User = Password = string() + Reason = euser | common_reason() + + +

Performs login of User with Password to the account + specified by Account.

+ + +
+
+ + + quote(Pid, Command) -> [FTPLine] + Sends an arbitrary FTP command. + + Pid = pid() + Command = string() + FTPLine = string( + +

The telnet end of line characters, from the FTP + protocol definition, CRLF, for example, "\\r\\n" has been removed.

+

Sends an arbitrary FTP command and returns verbatim a list + of the lines sent back by the FTP server. This function is + intended to give application accesses to FTP commands + that are server-specific or that cannot be provided by + this FTP client.

+ +

FTP commands requiring a data connection cannot be + successfully issued with this function.

+
+
+
+
+ +
+ ERRORS +

The possible error reasons and the corresponding diagnostic strings + returned by formaterror/1 are as follows: +

+ + echunk + +

Synchronization error during chunk sending according to one + of the following: +

+ A call is made to send_chunk/2 or send_chunk_end/1 + before a call to send_chunk_start/2. + A call has been made to another transfer function during chunk + sending, that is, before a call to send_chunk_end/1. + +
+ eclosed + +

The session is closed.

+
+ econn + +

Connection to the remote server is prematurely closed.

+
+ ehost + +

Host is not found, FTP server is not found, or connection is rejected + by FTP server.

+
+ elogin + +

User is not logged in.

+
+ enotbinary + +

Term is not a binary.

+
+ epath + +

No such file or directory, or directory already exists, or + permission denied.

+
+ etype + +

No such type.

+
+ euser + +

Invalid username or password.

+
+ etnospc + +

Insufficient storage space in system [452].

+
+ epnospc + +

Exceeded storage allocation (for current directory or + dataset) [552].

+
+ efnamena + +

Filename not allowed [553].

+
+
+
+ +
+ SEE ALSO +

file(3) + filename(3) + and J. Postel and J. Reynolds: File Transfer Protocol + (RFC 959). +

+
+ +
+ + diff --git a/lib/ftp/doc/src/ftp_client.xml b/lib/ftp/doc/src/ftp_client.xml new file mode 100644 index 0000000000..84705c1dba --- /dev/null +++ b/lib/ftp/doc/src/ftp_client.xml @@ -0,0 +1,84 @@ + + + + +
+ + 20042016 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + FTP Client + Ingela Anderton Andin + + + + + + + ftp_client.xml +
+ +
+ Getting Started + +

FTP clients are considered to be rather temporary. Thus, + they are only started and stopped during runtime and cannot + be started at application startup. + The FTP client API is designed to allow some functions to + return intermediate results. This implies that only the process + that started the FTP client can access it with + preserved sane semantics. + If the process that started the FTP session + dies, the FTP client process terminates.

+ +

The client supports IPv6 as long as the underlying mechanisms + also do so.

+ +

The following is a simple example of an FTP session, where + the user guest with password password logs on to + the remote host erlang.org:

+ inets:start(). + ok + 2> {ok, Pid} = inets:start(ftpc, [{host, "erlang.org"}]). + {ok,<0.22.0>} + 3> ftp:user(Pid, "guest", "password"). + ok + 4> ftp:pwd(Pid). + {ok, "/home/guest"} + 5> ftp:cd(Pid, "appl/examples"). + ok + 6> ftp:lpwd(Pid). + {ok, "/home/fred"}. + 7> ftp:lcd(Pid, "/home/eproj/examples"). + ok + 8> ftp:recv(Pid, "appl.erl"). + ok + 9> inets:stop(ftpc, Pid). + ok + ]]> +

The file + appl.erl is transferred from the remote to the local + host. When the session is opened, the current directory at + the remote host is /home/guest, and /home/fred + at the local host. Before transferring the file, the current + local directory is changed to /home/eproj/examples, and + the remote directory is set to + /home/guest/appl/examples.

+
+
diff --git a/lib/ftp/doc/src/introduction.xml b/lib/ftp/doc/src/introduction.xml new file mode 100644 index 0000000000..cc3673a0fc --- /dev/null +++ b/lib/ftp/doc/src/introduction.xml @@ -0,0 +1,46 @@ + + + + +
+ + 19972018 + Ericsson AB. All Rights Reserved. + + + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + + + Introduction + Péter Dimitrov + + + + + 2018-02-26 + A + introduction.xml +
+ +
+ Purpose +

An FTP client.

+
+ +
+ Prerequisites +

It is assumed that the reader is familiar with the Erlang + programming language, concepts of OTP, and has a basic + understanding of the FTP protocol.

+
+
diff --git a/lib/ftp/doc/src/notes.xml b/lib/ftp/doc/src/notes.xml new file mode 100644 index 0000000000..a8f4e18b51 --- /dev/null +++ b/lib/ftp/doc/src/notes.xml @@ -0,0 +1,53 @@ + + + + +
+ + 20022018 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + FTP Release Notes + + + + + + 2018-02-26 + A + notes.xml +
+ +
FTP 1.0.0 + +
First released version + + +

+ Inets application was split into multiple smaller protocol specific applications. + The FTP application is a standalone FTP client with the same functionality as + FTP client in Inets.

+

+ Own Id: OTP-14113

+
+
+
+ +
+ +
diff --git a/lib/ftp/doc/src/part.xml b/lib/ftp/doc/src/part.xml new file mode 100644 index 0000000000..148b588d8f --- /dev/null +++ b/lib/ftp/doc/src/part.xml @@ -0,0 +1,37 @@ + + + + +
+ + 20042018 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + Inets User's Guide + Péter Dimitrov + + 2018-02-26 + A + part.sgml +
+ +

The FTP application provides an FTP client.

+
+ + +
diff --git a/lib/ftp/doc/src/ref_man.xml b/lib/ftp/doc/src/ref_man.xml new file mode 100644 index 0000000000..a3936b02ed --- /dev/null +++ b/lib/ftp/doc/src/ref_man.xml @@ -0,0 +1,36 @@ + + + + +
+ + 19972018 + Ericsson AB. All Rights Reserved. + + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + Inets Reference Manual + Péter Dimitrov + + 2018-02-26 + 1.0 + ref_man.xml +
+ +

An FTP client.

+
+ +
diff --git a/lib/ftp/ebin/.gitignore b/lib/ftp/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ftp/info b/lib/ftp/info new file mode 100644 index 0000000000..f6c62d19c2 --- /dev/null +++ b/lib/ftp/info @@ -0,0 +1,2 @@ +group: comm +short: FTP client diff --git a/lib/ftp/src/Makefile b/lib/ftp/src/Makefile new file mode 100644 index 0000000000..d5b197d18d --- /dev/null +++ b/lib/ftp/src/Makefile @@ -0,0 +1,118 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-2017. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% +# + +# + +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(FTP_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/ftp-$(VSN) + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- + +BEHAVIOUR_MODULES= + +MODULES= \ + ftp \ + ftp_app \ + ftp_progress \ + ftp_response \ + ftp_sup + + +INTERNAL_HRL_FILES = + +ERL_FILES= \ + $(MODULES:%=%.erl) \ + $(BEHAVIOUR_MODULES:%=%.erl) + + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +BEHAVIOUR_TARGET_FILES= $(BEHAVIOUR_MODULES:%=$(EBIN)/%.$(EMULATOR)) + +APP_FILE= ftp.app +#APPUP_FILE= ftp.appup + +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBIN)/$(APP_FILE) +#APPUP_SRC= $(APPUP_FILE).src +#APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +EXTRA_ERLC_FLAGS = +warn_unused_vars +ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \ + -pz $(EBIN) \ + -pz $(ERL_TOP)/lib/public_key/ebin \ + $(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\" + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +$(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) + +debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + +clean: + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES) + rm -f errs core *~ + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + $(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@ + +docs: + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) "$(RELSYSDIR)/src" + $(INSTALL_DATA) $(ERL_FILES) $(INTERNAL_HRL_FILES) "$(RELSYSDIR)/src" + $(INSTALL_DIR) "$(RELSYSDIR)/ebin" + $(INSTALL_DATA) $(BEHAVIOUR_TARGET_FILES) $(TARGET_FILES) $(APP_TARGET) \ + $(APPUP_TARGET) "$(RELSYSDIR)/ebin" + +release_docs_spec: + +# ---------------------------------------------------- +# Dependencies +# ---------------------------------------------------- + diff --git a/lib/ftp/src/ftp.app.src b/lib/ftp/src/ftp.app.src new file mode 100644 index 0000000000..ac9ae2ac53 --- /dev/null +++ b/lib/ftp/src/ftp.app.src @@ -0,0 +1,16 @@ +{application, ftp, + [{description, "FTP client"}, + {vsn, "1.0.0"}, + {registered, []}, + {mod, { ftp_app, []}}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {maintainers, []}, + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/lib/ftp/src/ftp.erl b/lib/ftp/src/ftp.erl new file mode 100644 index 0000000000..6bf83184e3 --- /dev/null +++ b/lib/ftp/src/ftp.erl @@ -0,0 +1,2593 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(ftp). + +-behaviour(gen_server). + +-export([start/0, + start_service/1, + stop/0, + stop_service/1, + services/0, + service_info/1 + ]). + +-export([start_link/1, start_link/2]). + +%% API - Client interface +-export([cd/2, close/1, delete/2, formaterror/1, + lcd/2, lpwd/1, ls/1, ls/2, + mkdir/2, nlist/1, nlist/2, + open/1, open/2, + pwd/1, quote/2, + recv/2, recv/3, recv_bin/2, + recv_chunk_start/2, recv_chunk/1, + rename/3, rmdir/2, + send/2, send/3, send_bin/3, + send_chunk_start/2, send_chunk/2, send_chunk_end/1, + type/2, user/3, user/4, account/2, + append/3, append/2, append_bin/3, + append_chunk/2, append_chunk_end/1, append_chunk_start/2, + info/1, latest_ctrl_response/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, + handle_info/2, terminate/2, code_change/3]). + +-include("ftp_internal.hrl"). + +%% Constants used in internal state definition +-define(CONNECTION_TIMEOUT, 60*1000). +-define(DATA_ACCEPT_TIMEOUT, infinity). +-define(DEFAULT_MODE, passive). +-define(PROGRESS_DEFAULT, ignore). +-define(FTP_EXT_DEFAULT, false). + +%% Internal Constants +-define(FTP_PORT, 21). +-define(FILE_BUFSIZE, 4096). + + +%%%========================================================================= +%%% Data Types +%%%========================================================================= + +%% Internal state +-record(state, { + csock = undefined, % socket() - Control connection socket + dsock = undefined, % socket() - Data connection socket + tls_options = undefined, % list() + verbose = false, % boolean() + ldir = undefined, % string() - Current local directory + type = ftp_server_default, % atom() - binary | ascii + chunk = false, % boolean() - Receiving data chunks + mode = ?DEFAULT_MODE, % passive | active + timeout = ?CONNECTION_TIMEOUT, % integer() + %% Data received so far on the data connection + data = <<>>, % binary() + %% Data received so far on the control connection + %% {BinStream, AccLines}. If a binary sequence + %% ends with ?CR then keep it in the binary to + %% be able to detect if the next received byte is ?LF + %% and hence the end of the response is reached! + ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus} + %% pid() - Client pid (note not the same as "From") + latest_ctrl_response = "", + owner = undefined, + client = undefined, % "From" to be used in gen_server:reply/2 + %% Function that activated a connection and maybe some + %% data needed further on. + caller = undefined, % term() + ipfamily, % inet | inet6 | inet6fb4 + progress = ignore, % ignore | pid() + dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity + tls_upgrading_data_connection = false, + ftp_extension = ?FTP_EXT_DEFAULT + }). + +-record(recv_chunk_closing, { + dconn_closed = false, + pos_compl_received = false, + client_called_us = false + }). + + +-type shortage_reason() :: 'etnospc' | 'epnospc'. +-type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'. +-type common_reason() :: 'econn' | 'eclosed' | term(). +-type file_write_error_reason() :: term(). % See file:write for more info + +-define(DBG(F,A), 'n/a'). +%%-define(DBG(F,A), io:format(F,A)). +%%-define(DBG(F,A), ct:pal("~p:~p " ++ if is_list(F) -> F; is_atom(F) -> atom_to_list(F) end, [?MODULE,?LINE|A])). + + +%%%========================================================================= +%%% API +%%%========================================================================= + +start() -> + application:start(ftp). + +start_service(Options) -> + try + {ok, StartOptions} = start_options(Options), + {ok, OpenOptions} = open_options(Options), + case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of + {ok, Pid} -> + call(Pid, {open, ip_comm, OpenOptions}, plain); + Error1 -> + Error1 + end + catch + throw:Error2 -> + Error2 + end. + +stop() -> + application:stop(ftp). + +stop_service(Pid) -> + close(Pid). + +services() -> + [{ftpc, Pid} || {_, Pid, _, _} <- + supervisor:which_children(ftp_sup)]. +service_info(Pid) -> + {ok, Info} = call(Pid, info, list), + {ok, [proplists:lookup(mode, Info), + proplists:lookup(local_port, Info), + proplists:lookup(peer, Info), + proplists:lookup(peer_port, Info)]}. + + +%%%========================================================================= +%%% API - CLIENT FUNCTIONS +%%%========================================================================= + +%%-------------------------------------------------------------------------- +%% open(HostOrOtpList, , ) -> {ok, Pid} | {error, ehost} +%% HostOrOtpList = string() | [{option_list, Options}] +%% Port = integer(), +%% Flags = [Flag], +%% Flag = verbose | debug | trace +%% +%% Description: Start an ftp client and connect to a host. +%%-------------------------------------------------------------------------- + +-spec open(Host :: string() | inet:ip_address()) -> + {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}. + +%% +open({option_list, Options}) when is_list(Options) -> + try + {ok, StartOptions} = start_options(Options), + {ok, OpenOptions} = open_options(Options), + case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of + {ok, Pid} -> + call(Pid, {open, ip_comm, OpenOptions}, plain); + Error1 -> + Error1 + end + catch + throw:Error2 -> + Error2 + end; +%% + +open(Host) -> + open(Host, []). + +-spec open(Host :: string() | inet:ip_address(), Opts :: list()) -> + {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}. + +%% +open(Host, Port) when is_integer(Port) -> + open(Host, [{port, Port}]); +%% + +open(Host, Opts) when is_list(Opts) -> + ?fcrt("open", [{host, Host}, {opts, Opts}]), + try + {ok, StartOptions} = start_options(Opts), + ?fcrt("open", [{start_options, StartOptions}]), + {ok, OpenOptions} = open_options([{host, Host}|Opts]), + ?fcrt("open", [{open_options, OpenOptions}]), + case start_link(StartOptions, []) of + {ok, Pid} -> + do_open(Pid, OpenOptions, tls_options(Opts)); + Error1 -> + ?fcrt("open - error", [{error1, Error1}]), + Error1 + end + catch + throw:Error2 -> + ?fcrt("open - error", [{error2, Error2}]), + Error2 + end. + +do_open(Pid, OpenOptions, TLSOpts) -> + case call(Pid, {open, ip_comm, OpenOptions}, plain) of + {ok, Pid} -> + maybe_tls_upgrade(Pid, TLSOpts); + Error -> + Error + end. +%%-------------------------------------------------------------------------- +%% user(Pid, User, Pass, ) -> ok | {error, euser} | {error, econn} +%% | {error, eacct} +%% Pid = pid(), +%% User = Pass = Acc = string() +%% +%% Description: Login with or without a supplied account name. +%%-------------------------------------------------------------------------- +-spec user(Pid :: pid(), + User :: string(), + Pass :: string()) -> + 'ok' | {'error', Reason :: 'euser' | common_reason()}. + +user(Pid, User, Pass) -> + case {is_name_sane(User), is_name_sane(Pass)} of + {true, true} -> + call(Pid, {user, User, Pass}, atom); + _ -> + {error, euser} + end. + +-spec user(Pid :: pid(), + User :: string(), + Pass :: string(), + Acc :: string()) -> + 'ok' | {'error', Reason :: 'euser' | common_reason()}. + +user(Pid, User, Pass, Acc) -> + case {is_name_sane(User), is_name_sane(Pass), is_name_sane(Acc)} of + {true, true, true} -> + call(Pid, {user, User, Pass, Acc}, atom); + _ -> + {error, euser} + end. + + +%%-------------------------------------------------------------------------- +%% account(Pid, Acc) -> ok | {error, eacct} +%% Pid = pid() +%% Acc= string() +%% +%% Description: Set a user Account. +%%-------------------------------------------------------------------------- + +-spec account(Pid :: pid(), Acc :: string()) -> + 'ok' | {'error', Reason :: 'eacct' | common_reason()}. + +account(Pid, Acc) -> + case is_name_sane(Acc) of + true -> + call(Pid, {account, Acc}, atom); + _ -> + {error, eacct} + end. + + +%%-------------------------------------------------------------------------- +%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn} +%% Pid = pid() +%% Dir = string() +%% +%% Description: Get the current working directory at remote server. +%%-------------------------------------------------------------------------- + +-spec pwd(Pid :: pid()) -> + {'ok', Dir :: string()} | + {'error', Reason :: restriction_reason() | common_reason()}. + +pwd(Pid) -> + call(Pid, pwd, ctrl). + + +%%-------------------------------------------------------------------------- +%% lpwd(Pid) -> {ok, Dir} +%% Pid = pid() +%% Dir = string() +%% +%% Description: Get the current working directory at local server. +%%-------------------------------------------------------------------------- + +-spec lpwd(Pid :: pid()) -> + {'ok', Dir :: string()}. + +lpwd(Pid) -> + call(Pid, lpwd, string). + + +%%-------------------------------------------------------------------------- +%% cd(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} +%% Pid = pid() +%% Dir = string() +%% +%% Description: Change current working directory at remote server. +%%-------------------------------------------------------------------------- + +-spec cd(Pid :: pid(), Dir :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. + +cd(Pid, Dir) -> + case is_name_sane(Dir) of + true -> + call(Pid, {cd, Dir}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% lcd(Pid, Dir) -> ok | {error, epath} +%% Pid = pid() +%% Dir = string() +%% +%% Description: Change current working directory for the local client. +%%-------------------------------------------------------------------------- + +-spec lcd(Pid :: pid(), Dir :: string()) -> + 'ok' | {'error', Reason :: restriction_reason()}. + +lcd(Pid, Dir) -> + call(Pid, {lcd, Dir}, string). + + +%%-------------------------------------------------------------------------- +%% ls(Pid) -> Result +%% ls(Pid, ) -> Result +%% +%% Pid = pid() +%% Dir = string() +%% Result = {ok, Listing} | {error, Reason} +%% Listing = string() +%% Reason = epath | elogin | econn +%% +%% Description: Returns a list of files in long format. +%%-------------------------------------------------------------------------- + +-spec ls(Pid :: pid()) -> + {'ok', Listing :: string()} | + {'error', Reason :: restriction_reason() | common_reason()}. + +ls(Pid) -> + ls(Pid, ""). + +-spec ls(Pid :: pid(), Dir :: string()) -> + {'ok', Listing :: string()} | + {'error', Reason :: restriction_reason() | common_reason()}. + +ls(Pid, Dir) -> + case is_name_sane(Dir) of + true -> + call(Pid, {dir, long, Dir}, string); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% nlist(Pid) -> Result +%% nlist(Pid, Pathname) -> Result +%% +%% Pid = pid() +%% Pathname = string() +%% Result = {ok, Listing} | {error, Reason} +%% Listing = string() +%% Reason = epath | elogin | econn +%% +%% Description: Returns a list of files in short format +%%-------------------------------------------------------------------------- + +-spec nlist(Pid :: pid()) -> + {'ok', Listing :: string()} | + {'error', Reason :: restriction_reason() | common_reason()}. + +nlist(Pid) -> + nlist(Pid, ""). + +-spec nlist(Pid :: pid(), Pathname :: string()) -> + {'ok', Listing :: string()} | + {'error', Reason :: restriction_reason() | common_reason()}. + +nlist(Pid, Dir) -> + case is_name_sane(Dir) of + true -> + call(Pid, {dir, short, Dir}, string); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% rename(Pid, Old, New) -> ok | {error, epath} | {error, elogin} +%% | {error, econn} +%% Pid = pid() +%% CurrFile = NewFile = string() +%% +%% Description: Rename a file at remote server. +%%-------------------------------------------------------------------------- + +-spec rename(Pid :: pid(), Old :: string(), New :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. + +rename(Pid, Old, New) -> + case {is_name_sane(Old), is_name_sane(New)} of + {true, true} -> + call(Pid, {rename, Old, New}, string); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% delete(Pid, File) -> ok | {error, epath} | {error, elogin} | +%% {error, econn} +%% Pid = pid() +%% File = string() +%% +%% Description: Remove file at remote server. +%%-------------------------------------------------------------------------- + +-spec delete(Pid :: pid(), File :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. + +delete(Pid, File) -> + case is_name_sane(File) of + true -> + call(Pid, {delete, File}, string); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} +%% Pid = pid(), +%% Dir = string() +%% +%% Description: Make directory at remote server. +%%-------------------------------------------------------------------------- + +-spec mkdir(Pid :: pid(), Dir :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. + +mkdir(Pid, Dir) -> + case is_name_sane(Dir) of + true -> + call(Pid, {mkdir, Dir}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} +%% Pid = pid(), +%% Dir = string() +%% +%% Description: Remove directory at remote server. +%%-------------------------------------------------------------------------- + +-spec rmdir(Pid :: pid(), Dir :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. + +rmdir(Pid, Dir) -> + case is_name_sane(Dir) of + true -> + call(Pid, {rmdir, Dir}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn} +%% Pid = pid() +%% Type = ascii | binary +%% +%% Description: Set transfer type. +%%-------------------------------------------------------------------------- + +-spec type(Pid :: pid(), Type :: ascii | binary) -> + 'ok' | + {'error', Reason :: 'etype' | restriction_reason() | common_reason()}. + +type(Pid, Type) -> + call(Pid, {type, Type}, atom). + + +%%-------------------------------------------------------------------------- +%% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} | +%% {error, elogin} | {error, econn} +%% Pid = pid() +%% RemoteFileName = LocalFileName = string() +%% +%% Description: Transfer file from remote server. +%%-------------------------------------------------------------------------- + +-spec recv(Pid :: pid(), RemoteFileName :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | + common_reason() | + file_write_error_reason()}. + +recv(Pid, RemotFileName) -> + recv(Pid, RemotFileName, RemotFileName). + +-spec recv(Pid :: pid(), + RemoteFileName :: string(), + LocalFileName :: string()) -> + 'ok' | {'error', Reason :: term()}. + +recv(Pid, RemotFileName, LocalFileName) -> + case is_name_sane(RemotFileName) of + true -> + call(Pid, {recv, RemotFileName, LocalFileName}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin} +%% | {error, econn} +%% Pid = pid() +%% RemoteFile = string() +%% Bin = binary() +%% +%% Description: Transfer file from remote server into binary. +%%-------------------------------------------------------------------------- + +-spec recv_bin(Pid :: pid(), + RemoteFile :: string()) -> + {'ok', Bin :: binary()} | + {'error', Reason :: restriction_reason() | common_reason()}. + +recv_bin(Pid, RemoteFile) -> + case is_name_sane(RemoteFile) of + true -> + call(Pid, {recv_bin, RemoteFile}, bin); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} +%% | {error, econn} +%% Pid = pid() +%% RemoteFile = string() +%% +%% Description: Start receive of chunks of remote file. +%%-------------------------------------------------------------------------- + +-spec recv_chunk_start(Pid :: pid(), + RemoteFile :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. + +recv_chunk_start(Pid, RemoteFile) -> + case is_name_sane(RemoteFile) of + true -> + call(Pid, {recv_chunk_start, RemoteFile}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% recv_chunk(Pid, RemoteFile) -> ok | {ok, Bin} | {error, Reason} +%% Pid = pid() +%% RemoteFile = string() +%% +%% Description: Transfer file from remote server into binary in chunks +%%-------------------------------------------------------------------------- + +-spec recv_chunk(Pid :: pid()) -> + 'ok' | + {'ok', Bin :: binary()} | + {'error', Reason :: restriction_reason() | common_reason()}. + +recv_chunk(Pid) -> + call(Pid, recv_chunk, atom). + + +%%-------------------------------------------------------------------------- +%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} +%% | {error, elogin} +%% | {error, econn} +%% Pid = pid() +%% LocalFileName = RemotFileName = string() +%% +%% Description: Transfer file to remote server. +%%-------------------------------------------------------------------------- + +-spec send(Pid :: pid(), LocalFileName :: string()) -> + 'ok' | + {'error', Reason :: restriction_reason() | + common_reason() | + shortage_reason()}. + +send(Pid, LocalFileName) -> + send(Pid, LocalFileName, LocalFileName). + +-spec send(Pid :: pid(), + LocalFileName :: string(), + RemoteFileName :: string()) -> + 'ok' | + {'error', Reason :: restriction_reason() | + common_reason() | + shortage_reason()}. + +send(Pid, LocalFileName, RemotFileName) -> + case is_name_sane(RemotFileName) of + true -> + call(Pid, {send, LocalFileName, RemotFileName}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} +%% | {error, enotbinary} | {error, econn} +%% Pid = pid() +%% Bin = binary() +%% RemoteFile = string() +%% +%% Description: Transfer a binary to a remote file. +%%-------------------------------------------------------------------------- + +-spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) -> + 'ok' | + {'error', Reason :: restriction_reason() | + common_reason() | + shortage_reason()}. + +send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) -> + case is_name_sane(RemoteFile) of + true -> + call(Pid, {send_bin, Bin, RemoteFile}, atom); + _ -> + {error, efnamena} + end; +send_bin(_Pid, _Bin, _RemoteFile) -> + {error, enotbinary}. + + +%%-------------------------------------------------------------------------- +%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} +%% | {error, econn} +%% Pid = pid() +%% RemoteFile = string() +%% +%% Description: Start transfer of chunks to remote file. +%%-------------------------------------------------------------------------- + +-spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) -> + 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. + +send_chunk_start(Pid, RemoteFile) -> + case is_name_sane(RemoteFile) of + true -> + call(Pid, {send_chunk_start, RemoteFile}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | +%% {error, epath} | {error, econn} +%% Pid = pid() +%% RemoteFile = string() +%% +%% Description: Start append chunks of data to remote file. +%%-------------------------------------------------------------------------- + +-spec append_chunk_start(Pid :: pid(), RemoteFile :: string()) -> + 'ok' | {'error', Reason :: term()}. + +append_chunk_start(Pid, RemoteFile) -> + case is_name_sane(RemoteFile) of + true -> + call(Pid, {append_chunk_start, RemoteFile}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} +%% | {error, echunk} | {error, econn} +%% Pid = pid() +%% Bin = binary(). +%% +%% Purpose: Send chunk to remote file. +%%-------------------------------------------------------------------------- + +-spec send_chunk(Pid :: pid(), Bin :: binary()) -> + 'ok' | + {'error', Reason :: 'echunk' | + restriction_reason() | + common_reason()}. + +send_chunk(Pid, Bin) when is_binary(Bin) -> + call(Pid, {transfer_chunk, Bin}, atom); +send_chunk(_Pid, _Bin) -> + {error, enotbinary}. + + +%%-------------------------------------------------------------------------- +%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} +%% | {error, echunk} | {error, econn} +%% Pid = pid() +%% Bin = binary() +%% +%% Description: Append chunk to remote file. +%%-------------------------------------------------------------------------- + +-spec append_chunk(Pid :: pid(), Bin :: binary()) -> + 'ok' | + {'error', Reason :: 'echunk' | + restriction_reason() | + common_reason()}. + +append_chunk(Pid, Bin) when is_binary(Bin) -> + call(Pid, {transfer_chunk, Bin}, atom); +append_chunk(_Pid, _Bin) -> + {error, enotbinary}. + + +%%-------------------------------------------------------------------------- +%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} +%% | {error, econn} +%% Pid = pid() +%% +%% Description: End sending of chunks to remote file. +%%-------------------------------------------------------------------------- + +-spec send_chunk_end(Pid :: pid()) -> + 'ok' | + {'error', Reason :: restriction_reason() | + common_reason() | + shortage_reason()}. + +send_chunk_end(Pid) -> + call(Pid, chunk_end, atom). + + +%%-------------------------------------------------------------------------- +%% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} +%% | {error, econn} +%% Pid = pid() +%% +%% Description: End appending of chunks to remote file. +%%-------------------------------------------------------------------------- + +-spec append_chunk_end(Pid :: pid()) -> + 'ok' | + {'error', Reason :: restriction_reason() | + common_reason() | + shortage_reason()}. + +append_chunk_end(Pid) -> + call(Pid, chunk_end, atom). + + +%%-------------------------------------------------------------------------- +%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} +%% | {error, elogin} +%% | {error, econn} +%% Pid = pid() +%% LocalFileName = RemotFileName = string() +%% +%% Description: Append the local file to the remote file +%%-------------------------------------------------------------------------- + +-spec append(Pid :: pid(), LocalFileName :: string()) -> + 'ok' | + {'error', Reason :: 'epath' | + 'elogin' | + 'etnospc' | + 'epnospc' | + 'efnamena' | common_reason()}. + +append(Pid, LocalFileName) -> + append(Pid, LocalFileName, LocalFileName). + +-spec append(Pid :: pid(), + LocalFileName :: string(), + RemoteFileName :: string()) -> + 'ok' | {'error', Reason :: term()}. + +append(Pid, LocalFileName, RemotFileName) -> + case is_name_sane(RemotFileName) of + true -> + call(Pid, {append, LocalFileName, RemotFileName}, atom); + _ -> + {error, efnamena} + end. + + +%%-------------------------------------------------------------------------- +%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} +%% | {error, enotbinary} | {error, econn} +%% Pid = pid() +%% Bin = binary() +%% RemoteFile = string() +%% +%% Purpose: Append a binary to a remote file. +%%-------------------------------------------------------------------------- + +-spec append_bin(Pid :: pid(), + Bin :: binary(), + RemoteFile :: string()) -> + 'ok' | + {'error', Reason :: restriction_reason() | + common_reason() | + shortage_reason()}. + +append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) -> + case is_name_sane(RemoteFile) of + true -> + call(Pid, {append_bin, Bin, RemoteFile}, atom); + _ -> + {error, efnamena} + end; +append_bin(_Pid, _Bin, _RemoteFile) -> + {error, enotbinary}. + + +%%-------------------------------------------------------------------------- +%% quote(Pid, Cmd) -> list() +%% Pid = pid() +%% Cmd = string() +%% +%% Description: Send arbitrary ftp command. +%%-------------------------------------------------------------------------- + +-spec quote(Pid :: pid(), Cmd :: string()) -> list(). + +quote(Pid, Cmd) when is_list(Cmd) -> + call(Pid, {quote, Cmd}, atom). + + +%%-------------------------------------------------------------------------- +%% close(Pid) -> ok +%% Pid = pid() +%% +%% Description: End the ftp session. +%%-------------------------------------------------------------------------- + +-spec close(Pid :: pid()) -> 'ok'. + +close(Pid) -> + cast(Pid, close), + ok. + + +%%-------------------------------------------------------------------------- +%% formaterror(Tag) -> string() +%% Tag = atom() | {error, atom()} +%% +%% Description: Return diagnostics. +%%-------------------------------------------------------------------------- + +-spec formaterror(Tag :: term()) -> string(). + +formaterror(Tag) -> + ftp_response:error_string(Tag). + + +info(Pid) -> + call(Pid, info, list). + + +%%-------------------------------------------------------------------------- +%% latest_ctrl_response(Pid) -> string() +%% Pid = pid() +%% +%% Description: The latest received response from the server +%%-------------------------------------------------------------------------- + +-spec latest_ctrl_response(Pid :: pid()) -> string(). + +latest_ctrl_response(Pid) -> + call(Pid, latest_ctrl_response, string). + + +%%%======================================================================== +%%% gen_server callback functions +%%%======================================================================== + +%%------------------------------------------------------------------------- +%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} +%% Description: Initiates the erlang process that manages a ftp connection. +%%------------------------------------------------------------------------- +init(Options) -> + process_flag(trap_exit, true), + + %% Keep track of the client + {value, {client, Client}} = lists:keysearch(client, 1, Options), + erlang:monitor(process, Client), + + %% Make sure inet is started + _ = inet_db:start(), + + %% Where are we + {ok, Dir} = file:get_cwd(), + + %% Maybe activate dbg + case key_search(debug, Options, disable) of + trace -> + dbg:tracer(), + dbg:p(all, [call]), + {ok, _} = dbg:tpl(ftp, [{'_', [], [{return_trace}]}]), + {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]), + {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]), + ok; + debug -> + dbg:tracer(), + dbg:p(all, [call]), + {ok, _} = dbg:tp(ftp, [{'_', [], [{return_trace}]}]), + {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]), + {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]), + ok; + _ -> + %% Keep silent + ok + end, + + %% Verbose? + Verbose = key_search(verbose, Options, false), + + %% IpFamily? + IpFamily = key_search(ipfamily, Options, inet), + + State = #state{owner = Client, + verbose = Verbose, + ipfamily = IpFamily, + ldir = Dir}, + + %% Set process prio + Priority = key_search(priority, Options, low), + process_flag(priority, Priority), + + %% And we are done + {ok, State}. + + +%%-------------------------------------------------------------------------- +%% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% Description: Handle incoming requests. +%%------------------------------------------------------------------------- + +%% Anyone can ask this question +handle_call({_, info}, _, #state{verbose = Verbose, + mode = Mode, + timeout = Timeout, + ipfamily = IpFamily, + csock = Socket, + progress = Progress} = State) -> + {ok, {_, LocalPort}} = sockname(Socket), + {ok, {Address, Port}} = peername(Socket), + Options = [{verbose, Verbose}, + {ipfamily, IpFamily}, + {mode, Mode}, + {peer, Address}, + {peer_port, Port}, + {local_port, LocalPort}, + {timeout, Timeout}, + {progress, Progress}], + {reply, {ok, Options}, State}; + +handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = State) -> + {reply, {ok,Resp}, State}; + +%% But everything else must come from the owner +handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid -> + {reply, {error, not_connection_owner}, State}; + +handle_call({_, {open, ip_comm, Opts}}, From, State) -> + ?fcrd("handle_call(open)", [{opts, Opts}]), + case key_search(host, Opts, undefined) of + undefined -> + {stop, normal, {error, ehost}, State}; + Host -> + Mode = key_search(mode, Opts, ?DEFAULT_MODE), + Port = key_search(port, Opts, ?FTP_PORT), + Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), + DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), + Progress = key_search(progress, Opts, ignore), + IpFamily = key_search(ipfamily, Opts, inet), + FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), + + State2 = State#state{client = From, + mode = Mode, + progress = progress(Progress), + ipfamily = IpFamily, + dtimeout = DTimeout, + ftp_extension = FtpExt}, + + ?fcrd("handle_call(open) -> setup ctrl connection with", + [{host, Host}, {port, Port}, {timeout, Timeout}]), + case setup_ctrl_connection(Host, Port, Timeout, State2) of + {ok, State3, WaitTimeout} -> + ?fcrd("handle_call(open) -> ctrl connection setup done", + [{waittimeout, WaitTimeout}]), + {noreply, State3, WaitTimeout}; + {error, Reason} -> + ?fcrd("handle_call(open) -> ctrl connection setup failed", + [{reason, Reason}]), + gen_server:reply(From, {error, ehost}), + {stop, normal, State2#state{client = undefined}} + end + end; + +handle_call({_, {open, ip_comm, Host, Opts}}, From, State) -> + Mode = key_search(mode, Opts, ?DEFAULT_MODE), + Port = key_search(port, Opts, ?FTP_PORT), + Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), + DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), + Progress = key_search(progress, Opts, ignore), + FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), + + State2 = State#state{client = From, + mode = Mode, + progress = progress(Progress), + dtimeout = DTimeout, + ftp_extension = FtpExt}, + + case setup_ctrl_connection(Host, Port, Timeout, State2) of + {ok, State3, WaitTimeout} -> + {noreply, State3, WaitTimeout}; + {error, _Reason} -> + gen_server:reply(From, {error, ehost}), + {stop, normal, State2#state{client = undefined}} + end; + +handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State) -> + send_ctrl_message(State, mk_cmd("AUTH TLS", [])), + activate_ctrl_connection(State), + {noreply, State#state{client = From, caller = open, tls_options = TLSOptions}}; + +handle_call({_, {user, User, Password}}, From, + #state{csock = CSock} = State) when (CSock =/= undefined) -> + handle_user(User, Password, "", State#state{client = From}); + +handle_call({_, {user, User, Password, Acc}}, From, + #state{csock = CSock} = State) when (CSock =/= undefined) -> + handle_user(User, Password, Acc, State#state{client = From}); + +handle_call({_, {account, Acc}}, From, State)-> + handle_user_account(Acc, State#state{client = From}); + +handle_call({_, pwd}, From, #state{chunk = false} = State) -> + send_ctrl_message(State, mk_cmd("PWD", [])), + activate_ctrl_connection(State), + {noreply, State#state{client = From, caller = pwd}}; + +handle_call({_, lpwd}, From, #state{ldir = LDir} = State) -> + {reply, {ok, LDir}, State#state{client = From}}; + +handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State) -> + send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])), + activate_ctrl_connection(State), + {noreply, State#state{client = From, caller = cd}}; + +handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) -> + LDir = filename:absname(Dir, LDir0), + case file:read_file_info(LDir) of %% FIX better check that LDir is a dir. + {ok, _ } -> + {reply, ok, State#state{ldir = LDir}}; + _ -> + {reply, {error, epath}, State} + end; + +handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From, + #state{chunk = false} = State) -> + setup_data_connection(State#state{caller = {dir, Dir, Len}, + client = From}); +handle_call({_, {rename, CurrFile, NewFile}}, From, + #state{chunk = false} = State) -> + send_ctrl_message(State, mk_cmd("RNFR ~s", [CurrFile])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {rename, NewFile}, client = From}}; + +handle_call({_, {delete, File}}, {_Pid, _} = From, + #state{chunk = false} = State) -> + send_ctrl_message(State, mk_cmd("DELE ~s", [File])), + activate_ctrl_connection(State), + {noreply, State#state{client = From}}; + +handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State) -> + send_ctrl_message(State, mk_cmd("MKD ~s", [Dir])), + activate_ctrl_connection(State), + {noreply, State#state{client = From}}; + +handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State) -> + send_ctrl_message(State, mk_cmd("RMD ~s", [Dir])), + activate_ctrl_connection(State), + {noreply, State#state{client = From}}; + +handle_call({_,{type, Type}}, From, #state{chunk = false} = State) -> + case Type of + ascii -> + send_ctrl_message(State, mk_cmd("TYPE A", [])), + activate_ctrl_connection(State), + {noreply, State#state{caller = type, type = ascii, + client = From}}; + binary -> + send_ctrl_message(State, mk_cmd("TYPE I", [])), + activate_ctrl_connection(State), + {noreply, State#state{caller = type, type = binary, + client = From}}; + _ -> + {reply, {error, etype}, State} + end; + +handle_call({_,{recv, RemoteFile, LocalFile}}, From, + #state{chunk = false, ldir = LocalDir} = State) -> + progress_report({remote_file, RemoteFile}, State), + NewLocalFile = filename:absname(LocalFile, LocalDir), + + case file_open(NewLocalFile, write) of + {ok, Fd} -> + setup_data_connection(State#state{client = From, + caller = + {recv_file, + RemoteFile, Fd}}); + {error, _What} -> + {reply, {error, epath}, State} + end; + +handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} = + State) -> + setup_data_connection(State#state{caller = {recv_bin, RemoteFile}, + client = From}); + +handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false} + = State) -> + setup_data_connection(State#state{caller = {start_chunk_transfer, + "RETR", RemoteFile}, + client = From}); + +handle_call({_, recv_chunk}, _, #state{chunk = false} = State) -> + {reply, {error, "ftp:recv_chunk_start/2 not called"}, State}; + +handle_call({_, recv_chunk}, _From, #state{chunk = true, + caller = #recv_chunk_closing{dconn_closed = true, + pos_compl_received = true + } + } = State0) -> + %% The ftp:recv_chunk call was the last event we waited for, finnish and clean up + ?DBG("recv_chunk_closing ftp:recv_chunk, last event",[]), + activate_ctrl_connection(State0), + {reply, ok, State0#state{caller = undefined, + chunk = false, + client = undefined}}; + +handle_call({_, recv_chunk}, From, #state{chunk = true, + caller = #recv_chunk_closing{} = R + } = State) -> + %% Waiting for more, don't care what + ?DBG("recv_chunk_closing ftp:recv_chunk, get more",[]), + {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}}; + +handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) -> + State = activate_data_connection(State0), + {noreply, State#state{client = From, caller = recv_chunk}}; + +handle_call({_, {send, LocalFile, RemoteFile}}, From, + #state{chunk = false, ldir = LocalDir} = State) -> + progress_report({local_file, filename:absname(LocalFile, LocalDir)}, + State), + setup_data_connection(State#state{caller = {transfer_file, + {"STOR", + LocalFile, RemoteFile}}, + client = From}); +handle_call({_, {append, LocalFile, RemoteFile}}, From, + #state{chunk = false} = State) -> + setup_data_connection(State#state{caller = {transfer_file, + {"APPE", + LocalFile, RemoteFile}}, + client = From}); +handle_call({_, {send_bin, Bin, RemoteFile}}, From, + #state{chunk = false} = State) -> + setup_data_connection(State#state{caller = {transfer_data, + {"STOR", Bin, RemoteFile}}, + client = From}); +handle_call({_,{append_bin, Bin, RemoteFile}}, From, + #state{chunk = false} = State) -> + setup_data_connection(State#state{caller = {transfer_data, + {"APPE", Bin, RemoteFile}}, + client = From}); +handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false} + = State) -> + setup_data_connection(State#state{caller = {start_chunk_transfer, + "STOR", RemoteFile}, + client = From}); +handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false} + = State) -> + setup_data_connection(State#state{caller = {start_chunk_transfer, + "APPE", RemoteFile}, + client = From}); +handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) -> + send_data_message(State, Bin), + {reply, ok, State}; + +handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) -> + {reply, {error, echunk}, State}; + +handle_call({_, chunk_end}, From, #state{chunk = true} = State) -> + close_data_connection(State), + activate_ctrl_connection(State), + {noreply, State#state{client = From, dsock = undefined, + caller = end_chunk_transfer, chunk = false}}; + +handle_call({_, chunk_end}, _, #state{chunk = false} = State) -> + {reply, {error, echunk}, State}; + +handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State) -> + send_ctrl_message(State, mk_cmd(Cmd, [])), + activate_ctrl_connection(State), + {noreply, State#state{client = From, caller = quote}}; + +handle_call({_, _Req}, _From, #state{csock = CSock} = State) + when (CSock =:= undefined) -> + {reply, {error, not_connected}, State}; + +handle_call(_, _, #state{chunk = true} = State) -> + {reply, {error, echunk}, State}; + +%% Catch all - This can only happen if the application programmer writes +%% really bad code that violates the API. +handle_call(Request, _Timeout, State) -> + {stop, {'API_violation_connection_closed', Request}, + {error, {connection_terminated, 'API_violation'}}, State}. + +%%-------------------------------------------------------------------------- +%% handle_cast(Request, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handles cast messages. +%%------------------------------------------------------------------------- +handle_cast({Pid, close}, #state{owner = Pid} = State) -> + send_ctrl_message(State, mk_cmd("QUIT", [])), + close_ctrl_connection(State), + close_data_connection(State), + {stop, normal, State#state{csock = undefined, dsock = undefined}}; + +handle_cast({Pid, close}, State) -> + Report = io_lib:format("A none owner process ~p tried to close an " + "ftp connection: ~n", [Pid]), + error_logger:info_report(Report), + {noreply, State}; + +%% Catch all - This can oly happen if the application programmer writes +%% really bad code that violates the API. +handle_cast(Msg, State) -> + {stop, {'API_violation_connection_closed', Msg}, State}. + +%%-------------------------------------------------------------------------- +%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handles tcp messages from the ftp-server. +%% Note: The order of the function clauses is significant. +%%-------------------------------------------------------------------------- + +handle_info(timeout, #state{caller = open} = State) -> + {stop, timeout, State}; + +handle_info(timeout, State) -> + {noreply, State}; + +%%% Data socket messages %%% +handle_info({Trpt, Socket, Data}, + #state{dsock = {Trpt,Socket}, + caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl -> + ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]), + ok = file_write(binary_to_list(Data), Fd), + progress_report({binary, Data}, State0), + State = activate_data_connection(State0), + {noreply, State}; + +handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From, + caller = recv_chunk} + = State) when Trpt==tcp;Trpt==ssl -> + ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State]), + gen_server:reply(From, {ok, Data}), + {noreply, State#state{client = undefined, data = <<>>}}; + +handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when Trpt==tcp;Trpt==ssl -> + ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]), + State = activate_data_connection(State0), + {noreply, State#state{data = <<(State#state.data)/binary, + Data/binary>>}}; + +handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, + caller = {recv_file, Fd}} = State) + when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> + file_close(Fd), + progress_report({transfer_size, 0}, State), + activate_ctrl_connection(State), + ?DBG("Data channel close",[]), + {noreply, State#state{dsock = undefined, data = <<>>}}; + +handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, + client = Client, + caller = recv_chunk} = State) + when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> + ?DBG("Data channel close recv_chunk",[]), + activate_ctrl_connection(State), + {noreply, State#state{dsock = undefined, + caller = #recv_chunk_closing{dconn_closed = true, + client_called_us = Client =/= undefined} + }}; + +handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin, + data = Data} = State) + when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> + ?DBG("Data channel close",[]), + activate_ctrl_connection(State), + {noreply, State#state{dsock = undefined, data = <<>>, + caller = {recv_bin, Data}}}; + +handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data, + caller = {handle_dir_result, Dir}} + = State) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> + ?DBG("Data channel close",[]), + activate_ctrl_connection(State), + {noreply, State#state{dsock = undefined, + caller = {handle_dir_result, Dir, Data}, +% data = <>}}; + data = <<>>}}; + +handle_info({Err, Socket, Reason}, #state{dsock = {Trpt,Socket}, + client = From} = State) + when {Err,Trpt}=={tcp_error,tcp} ; {Err,Trpt}=={ssl_error,ssl} -> + gen_server:reply(From, {error, Reason}), + close_data_connection(State), + {noreply, State#state{dsock = undefined, client = undefined, + data = <<>>, caller = undefined, chunk = false}}; + +%%% Ctrl socket messages %%% +handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket}, + verbose = Verbose, + caller = Caller, + client = From, + ctrl_data = {CtrlData, AccLines, + LineStatus}} + = State) -> + ?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<>,State]), + case ftp_response:parse_lines(<>, + AccLines, LineStatus) of + {ok, Lines, NextMsgData} -> + verbose(Lines, Verbose, 'receive'), + CtrlResult = ftp_response:interpret(Lines), + case Caller of + quote -> + gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])), + {noreply, State#state{client = undefined, + caller = undefined, + latest_ctrl_response = Lines, + ctrl_data = {NextMsgData, [], + start}}}; + _ -> + ?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]), + handle_ctrl_result(CtrlResult, + State#state{latest_ctrl_response = Lines, + ctrl_data = + {NextMsgData, [], start}}) + end; + {continue, NewCtrlData} -> + ?DBG(' ...Continue... ctrl_data=~p~n',[NewCtrlData]), + activate_ctrl_connection(State), + {noreply, State#state{ctrl_data = NewCtrlData}} + end; + +%% If the server closes the control channel it is +%% the expected behavior that connection process terminates. +handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}}) + when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> + exit(normal); %% User will get error message from terminate/2 + +handle_info({Err, Socket, Reason}, _) when Err==tcp_error ; Err==ssl_error -> + Report = + io_lib:format("~p on socket: ~p for reason: ~p~n", + [Err, Socket, Reason]), + error_logger:error_report(Report), + %% If tcp does not work the only option is to terminate, + %% this is the expected behavior under these circumstances. + exit(normal); %% User will get error message from terminate/2 + +%% Monitor messages - if the process owning the ftp connection goes +%% down there is no point in continuing. +handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) -> + {stop, normal, State#state{client = undefined}}; + +handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) -> + {stop, normal, State#state{client = undefined}}; + +handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) -> + {stop, normal, State#state{client = undefined}}; + +handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) -> + {stop, {stopped, {'EXIT', Process, Reason}}, + State#state{client = undefined}}; + +handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) -> + Report = io_lib:format("Progress reporting stopped for reason ~p~n", + [Reason]), + error_logger:info_report(Report), + {noreply, State#state{progress = ignore}}; + +%% Catch all - throws away unknown messages (This could happen by "accident" +%% so we do not want to crash, but we make a log entry as it is an +%% unwanted behaviour.) +handle_info(Info, State) -> + Report = io_lib:format("ftp : ~p : Unexpected message: ~p~nState: ~p~n", + [self(), Info, State]), + error_logger:info_report(Report), + {noreply, State}. + +%%-------------------------------------------------------------------------- +%% terminate/2 and code_change/3 +%%-------------------------------------------------------------------------- +terminate(normal, State) -> + %% If terminate reason =/= normal the progress reporting process will + %% be killed by the exit signal. + progress_report(stop, State), + do_terminate({error, econn}, State); +terminate(Reason, State) -> + Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]), + error_logger:error_report(Report), + do_terminate({error, eclosed}, State). + +do_terminate(ErrorMsg, State) -> + close_data_connection(State), + close_ctrl_connection(State), + case State#state.client of + undefined -> + ok; + From -> + gen_server:reply(From, ErrorMsg) + end, + ok. + +code_change(_Vsn, State1, upgrade_from_pre_5_12) -> + {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, + Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1, + IpFamily = + if + (IPv6Disable =:= true) -> + inet; + true -> + inet6fb4 + end, + State2 = #state{csock = CSock, + dsock = DSock, + verbose = Verbose, + ldir = LDir, + type = Type, + chunk = Chunk, + mode = Mode, + timeout = Timeout, + data = Data, + ctrl_data = CtrlData, + owner = Owner, + client = Client, + caller = Caller, + ipfamily = IpFamily, + progress = Progress}, + {ok, State2}; + +code_change(_Vsn, State1, downgrade_to_pre_5_12) -> + #state{csock = CSock, + dsock = DSock, + verbose = Verbose, + ldir = LDir, + type = Type, + chunk = Chunk, + mode = Mode, + timeout = Timeout, + data = Data, + ctrl_data = CtrlData, + owner = Owner, + client = Client, + caller = Caller, + ipfamily = IpFamily, + progress = Progress} = State1, + IPv6Disable = + if + (IpFamily =:= inet) -> + true; + true -> + false + end, + State2 = + {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, + Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress}, + {ok, State2}; + +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + +%%%========================================================================= +%% Start/stop +%%%========================================================================= +%%-------------------------------------------------------------------------- +%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason} +%% +%% Description: Callback function for the ftp supervisor. It is called +%% : when start_service/1 calls ftp_sup:start_child/1 to start an +%% : instance of the ftp process. Also called by start_standalone/1 +%%-------------------------------------------------------------------------- +start_link([Opts, GenServerOptions]) -> + start_link(Opts, GenServerOptions). + +start_link(Opts, GenServerOptions) -> + case lists:keysearch(client, 1, Opts) of + {value, _} -> + %% Via the supervisor + gen_server:start_link(?MODULE, Opts, GenServerOptions); + false -> + Opts2 = [{client, self()} | Opts], + gen_server:start_link(?MODULE, Opts2, GenServerOptions) + end. + + +%%% Stop functionality is handled by close/1 + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== + +%%-------------------------------------------------------------------------- +%%% Help functions to handle_call and/or handle_ctrl_result +%%-------------------------------------------------------------------------- +%% User handling +handle_user(User, Password, Acc, State) -> + send_ctrl_message(State, mk_cmd("USER ~s", [User])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {handle_user, Password, Acc}}}. + +handle_user_passwd(Password, Acc, State) -> + send_ctrl_message(State, mk_cmd("PASS ~s", [Password])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {handle_user_passwd, Acc}}}. + +handle_user_account(Acc, State) -> + send_ctrl_message(State, mk_cmd("ACCT ~s", [Acc])), + activate_ctrl_connection(State), + {noreply, State#state{caller = handle_user_account}}. + + +%%-------------------------------------------------------------------------- +%% handle_ctrl_result +%%-------------------------------------------------------------------------- +handle_ctrl_result({tls_upgrade, _}, #state{csock = {tcp, Socket}, + tls_options = TLSOptions, + timeout = Timeout, + caller = open, client = From} + = State0) -> + ?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]), + case ssl:connect(Socket, TLSOptions, Timeout) of + {ok, TLSSocket} -> + State = State0#state{csock = {ssl,TLSSocket}}, + send_ctrl_message(State, mk_cmd("PBSZ 0", [])), + activate_ctrl_connection(State), + {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}} }; + {error, _} = Error -> + gen_server:reply(From, {Error, self()}), + {stop, normal, State0#state{client = undefined, + caller = undefined, + tls_upgrading_data_connection = false}} + end; + +handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State) -> + send_ctrl_message(State, mk_cmd("PROT P", [])), + activate_ctrl_connection(State), + {noreply, State#state{tls_upgrading_data_connection = {true, prot}}}; + +handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot}, + client = From} = State) -> + gen_server:reply(From, {ok, self()}), + {noreply, State#state{client = undefined, + caller = undefined, + tls_upgrading_data_connection = false}}; + +handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From} + = State) -> + gen_server:reply(From, {ok, self()}), + {noreply, State#state{client = undefined, + caller = undefined }}; +handle_ctrl_result({_, Lines}, #state{caller = open} = State) -> + ctrl_result_response(econn, State, {error, Lines}); + +%%-------------------------------------------------------------------------- +%% Data connection setup active mode +handle_ctrl_result({pos_compl, _Lines}, + #state{mode = active, + caller = {setup_data_connection, + {LSock, Caller}}} = State) -> + handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}}); + +handle_ctrl_result({Status, _Lines}, + #state{mode = active, + caller = {setup_data_connection, {LSock, _}}} + = State) -> + close_connection({tcp,LSock}), + ctrl_result_response(Status, State, {error, Status}); + +%% Data connection setup passive mode +handle_ctrl_result({pos_compl, Lines}, + #state{mode = passive, + ipfamily = inet6, + client = From, + caller = {setup_data_connection, Caller}, + csock = CSock, + timeout = Timeout} + = State) -> + [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), + {ok, {IP, _}} = peername(CSock), + case connect(IP, list_to_integer(PortStr), Timeout, State) of + {ok, _, Socket} -> + handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); + {error, _Reason} = Error -> + gen_server:reply(From, Error), + {noreply, State#state{client = undefined, caller = undefined}} + end; + +handle_ctrl_result({pos_compl, Lines}, + #state{mode = passive, + ipfamily = inet, + client = From, + caller = {setup_data_connection, Caller}, + timeout = Timeout, + ftp_extension = false} = State) -> + + {_, [?LEFT_PAREN | Rest]} = + lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines), + {NewPortAddr, _} = + lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest), + [A1, A2, A3, A4, P1, P2] = + lists:map(fun(X) -> list_to_integer(X) end, + string:tokens(NewPortAddr, [$,])), + IP = {A1, A2, A3, A4}, + Port = (P1 * 256) + P2, + + ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]), + case connect(IP, Port, Timeout, State) of + {ok, _, Socket} -> + handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}}); + {error, _Reason} = Error -> + gen_server:reply(From, Error), + {noreply,State#state{client = undefined, caller = undefined}} + end; + +handle_ctrl_result({pos_compl, Lines}, + #state{mode = passive, + ipfamily = inet, + client = From, + caller = {setup_data_connection, Caller}, + csock = CSock, + timeout = Timeout, + ftp_extension = true} = State) -> + + [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), + {ok, {IP, _}} = peername(CSock), + + ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]), + case connect(IP, list_to_integer(PortStr), Timeout, State) of + {ok, _, Socket} -> + handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); + {error, _Reason} = Error -> + gen_server:reply(From, Error), + {noreply, State#state{client = undefined, caller = undefined}} + end; + + +%% FTP server does not support passive mode: try to fallback on active mode +handle_ctrl_result(_, + #state{mode = passive, + caller = {setup_data_connection, Caller}} = State) -> + setup_data_connection(State#state{mode = active, caller = Caller}); + + +%%-------------------------------------------------------------------------- +%% User handling +handle_ctrl_result({pos_interm, _}, + #state{caller = {handle_user, PassWord, Acc}} = State) -> + handle_user_passwd(PassWord, Acc, State); +handle_ctrl_result({Status, _}, + #state{caller = {handle_user, _, _}} = State) -> + ctrl_result_response(Status, State, {error, euser}); + +%% Accounts +handle_ctrl_result({pos_interm_acct, _}, + #state{caller = {handle_user_passwd, Acc}} = State) + when Acc =/= "" -> + handle_user_account(Acc, State); +handle_ctrl_result({Status, _}, + #state{caller = {handle_user_passwd, _}} = State) -> + ctrl_result_response(Status, State, {error, euser}); + +%%-------------------------------------------------------------------------- +%% Print current working directory +handle_ctrl_result({pos_compl, Lines}, + #state{caller = pwd, client = From} = State) -> + Dir = pwd_result(Lines), + gen_server:reply(From, {ok, Dir}), + {noreply, State#state{client = undefined, caller = undefined}}; + +%%-------------------------------------------------------------------------- +%% Directory listing +handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) -> + case accept_data_connection(State0) of + {ok, State1} -> + State = activate_data_connection(State1), + {noreply, State#state{caller = {handle_dir_result, Dir}}}; + {error, _Reason} = ERROR -> + case State0#state.client of + undefined -> + {stop, ERROR, State0}; + From -> + gen_server:reply(From, ERROR), + {stop, normal, State0#state{client = undefined}} + end + end; + +handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir, + Data}, client = From} + = State) -> + case Dir of + "" -> % Current directory + gen_server:reply(From, {ok, Data}), + {noreply, State#state{client = undefined, + caller = undefined}}; + _ -> + %% + %% Dir cannot be assumed to be a dir. It is a string that + %% could be a dir, but could also be a file or even a string + %% containing wildcards (*). + %% + %% %% If there is only one line it might be a directory with one + %% %% file but it might be an error message that the directory + %% %% was not found. So in this case we have to endure a little + %% %% overhead to be able to give a good return value. Alas not + %% %% all ftp implementations behave the same and returning + %% %% an error string is allowed by the FTP RFC. + %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end, + %% binary_to_list(Data)) of + %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) -> + %% send_ctrl_message(State, mk_cmd("PWD", [])), + %% activate_ctrl_connection(State), + %% {noreply, + %% State#state{caller = {handle_dir_data, Dir, Data}}}; + %% _ -> + %% gen_server:reply(From, {ok, Data}), + %% {noreply, State#state{client = undefined, + %% caller = undefined}} + %% end + %% + gen_server:reply(From, {ok, Data}), + {noreply, State#state{client = undefined, + caller = undefined}} + end; + +handle_ctrl_result({pos_compl, Lines}, + #state{caller = {handle_dir_data, Dir, DirData}} = + State) -> + OldDir = pwd_result(Lines), + send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {handle_dir_data_second_phase, OldDir, + DirData}}}; +handle_ctrl_result({Status, _}, + #state{caller = {handle_dir_data, _, _}} = State) -> + ctrl_result_response(Status, State, {error, epath}); + +handle_ctrl_result(S={_Status, _}, + #state{caller = {handle_dir_result, _, _}} = State) -> + %% OTP-5731, macosx + ctrl_result_response(S, State, {error, epath}); + +handle_ctrl_result({pos_compl, _}, + #state{caller = {handle_dir_data_second_phase, OldDir, + DirData}} = State) -> + send_ctrl_message(State, mk_cmd("CWD ~s", [OldDir])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}}; +handle_ctrl_result({Status, _}, + #state{caller = {handle_dir_data_second_phase, _, _}} + = State) -> + ctrl_result_response(Status, State, {error, epath}); +handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData}, + client = From} = State) -> + gen_server:reply(From, {ok, DirData}), + {noreply, State#state{client = undefined, caller = undefined}}; + +handle_ctrl_result({Status, _}, #state{caller = cd} = State) -> + ctrl_result_response(Status, State, {error, Status}); + +handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) -> + ctrl_result_response(Status, State, {error, epath}); + +%%-------------------------------------------------------------------------- +%% File renaming +handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}} + = State) -> + send_ctrl_message(State, mk_cmd("RNTO ~s", [NewFile])), + activate_ctrl_connection(State), + {noreply, State#state{caller = rename_second_phase}}; + +handle_ctrl_result({Status, _}, + #state{caller = {rename, _}} = State) -> + ctrl_result_response(Status, State, {error, Status}); + +handle_ctrl_result({Status, _}, + #state{caller = rename_second_phase} = State) -> + ctrl_result_response(Status, State, {error, Status}); + +%%-------------------------------------------------------------------------- +%% File handling - recv_bin +handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) -> + case accept_data_connection(State0) of + {ok, State1} -> + State = activate_data_connection(State1), + {noreply, State}; + {error, _Reason} = ERROR -> + case State0#state.client of + undefined -> + {stop, ERROR, State0}; + From -> + gen_server:reply(From, ERROR), + {stop, normal, State0#state{client = undefined}} + end + end; + +handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data}, + client = From} = State) -> + gen_server:reply(From, {ok, Data}), + close_data_connection(State), + {noreply, State#state{client = undefined, caller = undefined}}; + +handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) -> + close_data_connection(State), + ctrl_result_response(Status, State#state{dsock = undefined}, + {error, epath}); + +handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) -> + close_data_connection(State), + ctrl_result_response(Status, State#state{dsock = undefined}, + {error, epath}); +%%-------------------------------------------------------------------------- +%% File handling - start_chunk_transfer +handle_ctrl_result({pos_prel, _}, #state{client = From, + caller = start_chunk_transfer} + = State0) -> + case accept_data_connection(State0) of + {ok, State1} -> + State = start_chunk(State1), + {noreply, State}; + {error, _Reason} = ERROR -> + case State0#state.client of + undefined -> + {stop, ERROR, State0}; + From -> + gen_server:reply(From, ERROR), + {stop, normal, State0#state{client = undefined}} + end + end; + +%%-------------------------------------------------------------------------- +%% File handling - chunk_transfer complete + +handle_ctrl_result({pos_compl, _}, #state{client = From, + caller = #recv_chunk_closing{dconn_closed = true, + client_called_us = true, + pos_compl_received = false + }} + = State0) when From =/= undefined -> + %% The pos_compl was the last event we waited for, finnish and clean up + ?DBG("recv_chunk_closing pos_compl, last event",[]), + gen_server:reply(From, ok), + activate_ctrl_connection(State0), + {noreply, State0#state{caller = undefined, + chunk = false, + client = undefined}}; + +handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R} + = State0) -> + %% Waiting for more, don't care what + ?DBG("recv_chunk_closing pos_compl, wait more",[]), + {noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}}; + + +%%-------------------------------------------------------------------------- +%% File handling - recv_file +handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) -> + case accept_data_connection(State0) of + {ok, State1} -> + State = activate_data_connection(State1), + {noreply, State}; + {error, _Reason} = ERROR -> + case State0#state.client of + undefined -> + {stop, ERROR, State0}; + From -> + gen_server:reply(From, ERROR), + {stop, normal, State0#state{client = undefined}} + end + end; + +handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) -> + file_close(Fd), + close_data_connection(State), + ctrl_result_response(Status, State#state{dsock = undefined}, + {error, epath}); +%%-------------------------------------------------------------------------- +%% File handling - transfer_* +handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}} + = State0) -> + case accept_data_connection(State0) of + {ok, State1} -> + send_file(State1, Fd); + {error, _Reason} = ERROR -> + case State0#state.client of + undefined -> + {stop, ERROR, State0}; + From -> + gen_server:reply(From, ERROR), + {stop, normal, State0#state{client = undefined}} + end + end; + +handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} + = State0) -> + case accept_data_connection(State0) of + {ok, State} -> + send_bin(State, Bin); + {error, _Reason} = ERROR -> + case State0#state.client of + undefined -> + {stop, ERROR, State0}; + From -> + gen_server:reply(From, ERROR), + {stop, normal, State0#state{client = undefined}} + end + end; + +%%-------------------------------------------------------------------------- +%% Default +handle_ctrl_result({Status, _Lines}, #state{client = From} = State) + when From =/= undefined -> + ctrl_result_response(Status, State, {error, Status}). + +%%-------------------------------------------------------------------------- +%% Help functions to handle_ctrl_result +%%-------------------------------------------------------------------------- +ctrl_result_response(pos_compl, #state{client = From} = State, _) -> + gen_server:reply(From, ok), + {noreply, State#state{client = undefined, caller = undefined}}; + +ctrl_result_response(enofile, #state{client = From} = State, _) -> + gen_server:reply(From, {error, enofile}), + {noreply, State#state{client = undefined, caller = undefined}}; + +ctrl_result_response(Status, #state{client = From} = State, _) + when (Status =:= etnospc) orelse + (Status =:= epnospc) orelse + (Status =:= efnamena) orelse + (Status =:= econn) -> + gen_server:reply(From, {error, Status}), +%% {stop, normal, {error, Status}, State#state{client = undefined}}; + {stop, normal, State#state{client = undefined}}; + +ctrl_result_response(_, #state{client = From} = State, ErrorMsg) -> + gen_server:reply(From, ErrorMsg), + {noreply, State#state{client = undefined, caller = undefined}}. + +%%-------------------------------------------------------------------------- +handle_caller(#state{caller = {dir, Dir, Len}} = State) -> + Cmd = case Len of + short -> "NLST"; + long -> "LIST" + end, + case Dir of + "" -> + send_ctrl_message(State, mk_cmd(Cmd, "")); + _ -> + send_ctrl_message(State, mk_cmd(Cmd ++ " ~s", [Dir])) + end, + activate_ctrl_connection(State), + {noreply, State#state{caller = {dir, Dir}}}; + +handle_caller(#state{caller = {recv_bin, RemoteFile}} = State) -> + send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), + activate_ctrl_connection(State), + {noreply, State#state{caller = recv_bin}}; + +handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} = + State) -> + send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), + activate_ctrl_connection(State), + {noreply, State#state{caller = start_chunk_transfer}}; + +handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State) -> + send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {recv_file, Fd}}}; + +handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}}, + ldir = LocalDir, client = From} = State) -> + case file_open(filename:absname(LocalFile, LocalDir), read) of + {ok, Fd} -> + send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {transfer_file, Fd}}}; + {error, _} -> + gen_server:reply(From, {error, epath}), + {noreply, State#state{client = undefined, caller = undefined, + dsock = undefined}} + end; + +handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = + State) -> + send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {transfer_data, Bin}}}. + +%% ----------- FTP SERVER COMMUNICATION ------------------------- + +%% Connect to FTP server at Host (default is TCP port 21) +%% in order to establish a control connection. +setup_ctrl_connection(Host, Port, Timeout, State) -> + MsTime = erlang:monotonic_time(), + case connect(Host, Port, Timeout, State) of + {ok, IpFam, CSock} -> + NewState = State#state{csock = {tcp, CSock}, ipfamily = IpFam}, + activate_ctrl_connection(NewState), + case Timeout - inets_lib:millisec_passed(MsTime) of + Timeout2 when (Timeout2 >= 0) -> + {ok, NewState#state{caller = open}, Timeout2}; + _ -> + %% Oups: Simulate timeout + {ok, NewState#state{caller = open}, 0} + end; + Error -> + Error + end. + +setup_data_connection(#state{mode = active, + caller = Caller, + csock = CSock, + ftp_extension = FtpExt} = State) -> + case (catch sockname(CSock)) of + {ok, {{_, _, _, _, _, _, _, _} = IP, _}} -> + {ok, LSock} = + gen_tcp:listen(0, [{ip, IP}, {active, false}, + inet6, binary, {packet, 0}]), + {ok, {_, Port}} = sockname({tcp,LSock}), + IpAddress = inet_parse:ntoa(IP), + Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]), + send_ctrl_message(State, Cmd), + activate_ctrl_connection(State), + {noreply, State#state{caller = {setup_data_connection, + {LSock, Caller}}}}; + {ok, {{_,_,_,_} = IP, _}} -> + {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, + binary, {packet, 0}]), + {ok, Port} = inet:port(LSock), + case FtpExt of + false -> + {IP1, IP2, IP3, IP4} = IP, + {Port1, Port2} = {Port div 256, Port rem 256}, + send_ctrl_message(State, + mk_cmd("PORT ~w,~w,~w,~w,~w,~w", + [IP1, IP2, IP3, IP4, Port1, Port2])); + true -> + IpAddress = inet_parse:ntoa(IP), + Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]), + send_ctrl_message(State, Cmd) + end, + activate_ctrl_connection(State), + {noreply, State#state{caller = {setup_data_connection, + {LSock, Caller}}}} + end; + +setup_data_connection(#state{mode = passive, ipfamily = inet6, + caller = Caller} = State) -> + send_ctrl_message(State, mk_cmd("EPSV", [])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {setup_data_connection, Caller}}}; + +setup_data_connection(#state{mode = passive, ipfamily = inet, + caller = Caller, + ftp_extension = false} = State) -> + send_ctrl_message(State, mk_cmd("PASV", [])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {setup_data_connection, Caller}}}; + +setup_data_connection(#state{mode = passive, ipfamily = inet, + caller = Caller, + ftp_extension = true} = State) -> + send_ctrl_message(State, mk_cmd("EPSV", [])), + activate_ctrl_connection(State), + {noreply, State#state{caller = {setup_data_connection, Caller}}}. + +connect(Host, Port, Timeout, #state{ipfamily = inet = IpFam}) -> + connect2(Host, Port, IpFam, Timeout); + +connect(Host, Port, Timeout, #state{ipfamily = inet6 = IpFam}) -> + connect2(Host, Port, IpFam, Timeout); + +connect(Host, Port, Timeout, #state{ipfamily = inet6fb4}) -> + case inet:getaddr(Host, inet6) of + {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} -> + case inet:getaddr(Host, inet) of + {ok, IPv4} -> + IpFam = inet, + connect2(IPv4, Port, IpFam, Timeout); + + _ -> + IpFam = inet6, + connect2(IPv6, Port, IpFam, Timeout) + end; + + {ok, IPv6} -> + IpFam = inet6, + connect2(IPv6, Port, IpFam, Timeout); + + _ -> + case inet:getaddr(Host, inet) of + {ok, IPv4} -> + IpFam = inet, + connect2(IPv4, Port, IpFam, Timeout); + Error -> + Error + end + end. + +connect2(Host, Port, IpFam, Timeout) -> + Opts = [IpFam, binary, {packet, 0}, {active, false}], + case gen_tcp:connect(Host, Port, Opts, Timeout) of + {ok, Sock} -> + {ok, IpFam, Sock}; + Error -> + Error + end. + + +accept_data_connection(#state{mode = active, + dtimeout = DTimeout, + tls_options = TLSOptions, + dsock = {lsock, LSock}} = State0) -> + case gen_tcp:accept(LSock, DTimeout) of + {ok, Socket} when is_list(TLSOptions) -> + gen_tcp:close(LSock), + ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]), + case ssl:connect(Socket, TLSOptions, DTimeout) of + {ok, TLSSocket} -> + {ok, State0#state{dsock={ssl,TLSSocket}}}; + {error, Reason} -> + {error, {ssl_connect_failed, Reason}} + end; + {ok, Socket} -> + gen_tcp:close(LSock), + {ok, State0#state{dsock={tcp,Socket}}}; + {error, Reason} -> + {error, {data_connect_failed, Reason}} + end; + +accept_data_connection(#state{mode = passive, + dtimeout = DTimeout, + dsock = {tcp,Socket}, + tls_options = TLSOptions} = State) when is_list(TLSOptions) -> + ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State]), + case ssl:connect(Socket, TLSOptions, DTimeout) of + {ok, TLSSocket} -> + {ok, State#state{dsock={ssl,TLSSocket}}}; + {error, Reason} -> + {error, {ssl_connect_failed, Reason}} + end; +accept_data_connection(#state{mode = passive} = State) -> + {ok,State}. + + +send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) -> + verbose(lists:flatten(Message),Verbose,send), + ?DBG('<--ctrl ~p ---- ~s~p~n',[Socket,Message,_S]), + _ = send_message(Socket, Message). + +send_data_message(_S=#state{dsock = Socket}, Message) -> + ?DBG('<==data ~p ==== ~s~n~p~n',[Socket,Message,_S]), + case send_message(Socket, Message) of + ok -> + ok; + {error, Reason} -> + Report = io_lib:format("send/2 for socket ~p failed with " + "reason ~p~n", [Socket, Reason]), + error_logger:error_report(Report), + %% If tcp/ssl does not work the only option is to terminate, + %% this is the expected behavior under these circumstances. + exit(normal) %% User will get error message from terminate/2 + end. + +send_message({tcp, Socket}, Message) -> + gen_tcp:send(Socket, Message); +send_message({ssl, Socket}, Message) -> + ssl:send(Socket, Message). + +activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}}) -> + activate_connection(CSock); +activate_ctrl_connection(#state{csock = CSock}) -> + activate_connection(CSock), + %% We have already received at least part of the next control message, + %% that has been saved in ctrl_data, process this first. + self() ! {socket_type(CSock), unwrap_socket(CSock), <<>>}, + ok. + +activate_data_connection(#state{dsock = DSock} = State) -> + activate_connection(DSock), + State. + +activate_connection(Socket) -> + ignore_return_value( + case socket_type(Socket) of + tcp -> inet:setopts(unwrap_socket(Socket), [{active, once}]); + ssl -> ssl:setopts(unwrap_socket(Socket), [{active, once}]) + end). + + +ignore_return_value(_) -> ok. + +unwrap_socket({tcp,Socket}) -> Socket; +unwrap_socket({ssl,Socket}) -> Socket. + +socket_type({tcp,_Socket}) -> tcp; +socket_type({ssl,_Socket}) -> ssl. + +close_ctrl_connection(#state{csock = undefined}) -> ok; +close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket). + +close_data_connection(#state{dsock = undefined}) -> ok; +close_data_connection(#state{dsock = Socket}) -> close_connection(Socket). + +close_connection({lsock,Socket}) -> ignore_return_value( gen_tcp:close(Socket) ); +close_connection({tcp, Socket}) -> ignore_return_value( gen_tcp:close(Socket) ); +close_connection({ssl, Socket}) -> ignore_return_value( ssl:close(Socket) ). + +%% ------------ FILE HANDLING ---------------------------------------- +send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) -> + {noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}}; +send_file(State, Fd) -> + case file_read(Fd) of + {ok, N, Bin} when N > 0 -> + send_data_message(State, Bin), + progress_report({binary, Bin}, State), + send_file(State, Fd); + {ok, _, _} -> + file_close(Fd), + close_data_connection(State), + progress_report({transfer_size, 0}, State), + activate_ctrl_connection(State), + {noreply, State#state{caller = transfer_file_second_phase, + dsock = undefined}}; + {error, Reason} -> + gen_server:reply(State#state.client, {error, Reason}), + {stop, normal, State#state{client = undefined}} + end. + +file_open(File, Option) -> + file:open(File, [raw, binary, Option]). + +file_close(Fd) -> + ignore_return_value( file:close(Fd) ). + +file_read(Fd) -> + case file:read(Fd, ?FILE_BUFSIZE) of + {ok, Bytes} -> + {ok, size(Bytes), Bytes}; + eof -> + {ok, 0, []}; + Other -> + Other + end. + +file_write(Bytes, Fd) -> + file:write(Fd, Bytes). + +%% -------------- MISC ---------------------------------------------- + +call(GenServer, Msg, Format) -> + call(GenServer, Msg, Format, infinity). +call(GenServer, Msg, Format, Timeout) -> + Req = {self(), Msg}, + case (catch gen_server:call(GenServer, Req, Timeout)) of + {ok, Bin} when is_binary(Bin) andalso (Format =:= string) -> + {ok, binary_to_list(Bin)}; + {'EXIT', _} -> + {error, eclosed}; + Result -> + Result + end. + +cast(GenServer, Msg) -> + gen_server:cast(GenServer, {self(), Msg}). + +send_bin(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Bin) -> + State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_bin, Bin}}; +send_bin(State, Bin) -> + send_data_message(State, Bin), + close_data_connection(State), + activate_ctrl_connection(State), + {noreply, State#state{caller = transfer_data_second_phase, + dsock = undefined}}. + +mk_cmd(Fmt, Args) -> + [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok. + +is_name_sane([]) -> + true; +is_name_sane([?CR| _]) -> + false; +is_name_sane([?LF| _]) -> + false; +is_name_sane([_| Rest]) -> + is_name_sane(Rest). + +pwd_result(Lines) -> + {_, [?DOUBLE_QUOTE | Rest]} = + lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines), + {Dir, _} = + lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest), + Dir. + + +key_search(Key, List, Default) -> + case lists:keysearch(Key, 1, List) of + {value, {_,Val}} -> + Val; + false -> + Default + end. + +verbose(Lines, true, Direction) -> + DirStr = + case Direction of + send -> + "Sending: "; + _ -> + "Receiving: " + end, + Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR), + erlang:display(DirStr++Str); +verbose(_, false,_) -> + ok. + +progress(Options) -> + ftp_progress:start_link(Options). + +progress_report(_, #state{progress = ignore}) -> + ok; +progress_report(stop, #state{progress = ProgressPid}) -> + ftp_progress:stop(ProgressPid); +progress_report({binary, Data}, #state{progress = ProgressPid}) -> + ftp_progress:report(ProgressPid, {transfer_size, size(Data)}); +progress_report(Report, #state{progress = ProgressPid}) -> + ftp_progress:report(ProgressPid, Report). + + +peername({tcp, Socket}) -> inet:peername(Socket); +peername({ssl, Socket}) -> ssl:peername(Socket). + +sockname({tcp, Socket}) -> inet:sockname(Socket); +sockname({ssl, Socket}) -> ssl:sockname(Socket). + +maybe_tls_upgrade(Pid, undefined) -> + {ok, Pid}; +maybe_tls_upgrade(Pid, TLSOptions) -> + catch ssl:start(), + call(Pid, {open, tls_upgrade, TLSOptions}, plain). + +start_chunk(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State) -> + State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, start_chunk, undefined}}; +start_chunk(#state{client = From} = State) -> + gen_server:reply(From, ok), + State#state{chunk = true, + client = undefined, + caller = undefined}. + + +%% This function extracts the start options from the +%% Valid options: +%% debug, +%% verbose +%% ipfamily +%% priority +%% flags (for backward compatibillity) +start_options(Options) -> + ?fcrt("start_options", [{options, Options}]), + case lists:keysearch(flags, 1, Options) of + {value, {flags, Flags}} -> + Verbose = lists:member(verbose, Flags), + IsTrace = lists:member(trace, Flags), + IsDebug = lists:member(debug, Flags), + DebugLevel = + if + (IsTrace =:= true) -> + trace; + IsDebug =:= true -> + debug; + true -> + disable + end, + {ok, [{verbose, Verbose}, + {debug, DebugLevel}, + {priority, low}]}; + false -> + ValidateVerbose = + fun(true) -> true; + (false) -> true; + (_) -> false + end, + ValidateDebug = + fun(trace) -> true; + (debug) -> true; + (disable) -> true; + (_) -> false + end, + ValidatePriority = + fun(low) -> true; + (normal) -> true; + (high) -> true; + (_) -> false + end, + ValidOptions = + [{verbose, ValidateVerbose, false, false}, + {debug, ValidateDebug, false, disable}, + {priority, ValidatePriority, false, low}], + validate_options(Options, ValidOptions, []) + end. + + +%% This function extracts and validates the open options from the +%% Valid options: +%% mode +%% host +%% port +%% timeout +%% dtimeout +%% progress +%% ftp_extension + +open_options(Options) -> + ?fcrt("open_options", [{options, Options}]), + ValidateMode = + fun(active) -> true; + (passive) -> true; + (_) -> false + end, + ValidateHost = + fun(Host) when is_list(Host) -> + true; + (Host) when is_tuple(Host) andalso + ((size(Host) =:= 4) orelse (size(Host) =:= 8)) -> + true; + (_) -> + false + end, + ValidatePort = + fun(Port) when is_integer(Port) andalso (Port > 0) -> true; + (_) -> false + end, + ValidateIpFamily = + fun(inet) -> true; + (inet6) -> true; + (inet6fb4) -> true; + (_) -> false + end, + ValidateTimeout = + fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true; + (_) -> false + end, + ValidateDTimeout = + fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true; + (infinity) -> true; + (_) -> false + end, + ValidateProgress = + fun(ignore) -> + true; + ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso + is_atom(Func) -> + true; + (_) -> + false + end, + ValidateFtpExtension = + fun(true) -> true; + (false) -> true; + (_) -> false + end, + ValidOptions = + [{mode, ValidateMode, false, ?DEFAULT_MODE}, + {host, ValidateHost, true, ehost}, + {port, ValidatePort, false, ?FTP_PORT}, + {ipfamily, ValidateIpFamily, false, inet}, + {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT}, + {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT}, + {progress, ValidateProgress, false, ?PROGRESS_DEFAULT}, + {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}], + validate_options(Options, ValidOptions, []). + +tls_options(Options) -> + %% Options will be validated by ssl application + proplists:get_value(tls, Options, undefined). + +validate_options([], [], Acc) -> + ?fcrt("validate_options -> done", [{acc, Acc}]), + {ok, lists:reverse(Acc)}; +validate_options([], ValidOptions, Acc) -> + ?fcrt("validate_options -> done", + [{valid_options, ValidOptions}, {acc, Acc}]), + %% Check if any mandatory options are missing! + case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of + [] -> + Defaults = + [{Key, Default} || {Key, _, _, Default} <- ValidOptions], + {ok, lists:reverse(Defaults ++ Acc)}; + [{_, Reason}|_Missing] -> + throw({error, Reason}) + end; +validate_options([{Key, Value}|Options], ValidOptions, Acc) -> + ?fcrt("validate_options -> check", + [{key, Key}, {value, Value}, {acc, Acc}]), + case lists:keysearch(Key, 1, ValidOptions) of + {value, {Key, Validate, _, Default}} -> + case (catch Validate(Value)) of + true -> + ?fcrt("validate_options -> check - accept", []), + NewValidOptions = lists:keydelete(Key, 1, ValidOptions), + validate_options(Options, NewValidOptions, + [{Key, Value} | Acc]); + _ -> + ?fcrt("validate_options -> check - reject", + [{default, Default}]), + NewValidOptions = lists:keydelete(Key, 1, ValidOptions), + validate_options(Options, NewValidOptions, + [{Key, Default} | Acc]) + end; + false -> + validate_options(Options, ValidOptions, Acc) + end; +validate_options([_|Options], ValidOptions, Acc) -> + validate_options(Options, ValidOptions, Acc). diff --git a/lib/ftp/src/ftp_app.erl b/lib/ftp/src/ftp_app.erl new file mode 100644 index 0000000000..d647d9fce3 --- /dev/null +++ b/lib/ftp/src/ftp_app.erl @@ -0,0 +1,47 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +%%%------------------------------------------------------------------- +%% @doc ftp public API +%% @end +%%%------------------------------------------------------------------- + +-module(ftp_app). + +-behaviour(application). + +%% Application callbacks +-export([start/2, stop/1]). + +%%==================================================================== +%% API +%%==================================================================== + +start(_StartType, _StartArgs) -> + ftp_sup:start_link(). + +%%-------------------------------------------------------------------- +stop(_State) -> + ok. + +%%==================================================================== +%% Internal functions +%%==================================================================== diff --git a/lib/ftp/src/ftp_internal.hrl b/lib/ftp/src/ftp_internal.hrl new file mode 100644 index 0000000000..3c6c4d5e0b --- /dev/null +++ b/lib/ftp/src/ftp_internal.hrl @@ -0,0 +1,59 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-ifndef(ftp_internal_hrl). +-define(ftp_internal_hrl, true). + +%%-include_lib("inets/src/inets_app/inets_internal.hrl"). + +%% Various trace macros + +-define(report(Severity, Label, Service, Content), + inets_trace:report_event(Severity, Label, Service, + [{module, ?MODULE}, {line, ?LINE} | Content])). +-define(report_important(Label, Service, Content), + ?report(20, Label, Service, Content)). +-define(report_verbose(Label, Service, Content), + ?report(40, Label, Service, Content)). +-define(report_debug(Label, Service, Content), + ?report(60, Label, Service, Content)). +-define(report_trace(Label, Service, Content), + ?report(80, Label, Service, Content)). + + +-define(CR, $\r). +-define(LF, $\n). +-define(CRLF, [$\r,$\n]). +-define(SP, $\s). +-define(TAB, $\t). +-define(LEFT_PAREN, $(). +-define(RIGHT_PAREN, $)). +-define(WHITE_SPACE, $ ). +-define(DOUBLE_QUOTE, $"). + + +-define(SERVICE, ftpc). +-define(fcri(Label, Content), ?report_important(Label, ?SERVICE, Content)). +-define(fcrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)). +-define(fcrd(Label, Content), ?report_debug(Label, ?SERVICE, Content)). +-define(fcrt(Label, Content), ?report_trace(Label, ?SERVICE, Content)). + +-endif. % -ifdef(ftp_internal_hrl). diff --git a/lib/ftp/src/ftp_progress.erl b/lib/ftp/src/ftp_progress.erl new file mode 100644 index 0000000000..a6263e5cd7 --- /dev/null +++ b/lib/ftp/src/ftp_progress.erl @@ -0,0 +1,136 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%% Description: This module impements a temporary process that +%% performes progress reporting during file transfer calling a user +%% defined callback function. Its life span is as long as the ftp connection +%% processes that spawned it lives. The purpose of this process is to +%% shild the ftp connection process from errors and time consuming operations +%% in the user defined callback function. + +-module(ftp_progress). + +%% Internal API +-export([start_link/1, report/2, stop/1]). + +%% Spawn export +-export([init/1]). + +-include_lib("kernel/include/file.hrl"). + +-record(progress, { + file :: string() | 'undefined', + cb_module :: module(), + cb_function :: atom(), + init_progress_term :: term(), + current_progress_term :: term() + }). + +%%%========================================================================= +%%% Internal application API +%%%========================================================================= +%%-------------------------------------------------------------------------- +%% start_link(Options) -> ignore | pid() +%% Options = ignore | {CBModule, CBFunction, InitProgressTerm} +%% +%% Description: Starts the progress report process unless progress reporting +%% should not be performed. +%%-------------------------------------------------------------------------- +-type options() :: 'ignore' | {module(), atom(), term()}. +-spec start_link(options()) -> 'ignore' | pid(). +start_link(ignore) -> + ignore; +start_link(Options) -> + spawn_link(?MODULE, init, [Options]). + +%%-------------------------------------------------------------------------- +%% report_progress(Pid, Report) -> ok +%% Pid = pid() +%% Report = {local_file, File} | {remote_file, File} | +%% {transfer_size, Size} +%% Size = integer() +%% +%% Description: Reports progress to the reporting process that calls the +%% user defined callback function. +%%-------------------------------------------------------------------------- +-type report() :: {'local_file', string()} | {'remote_file', string()} + | {'transfer_size', non_neg_integer()}. +-spec report(pid(), report()) -> 'ok'. +report(Pid, Report) -> + Pid ! {progress_report, Report}, + ok. + +%%-------------------------------------------------------------------------- +%% stop(Pid) -> ok +%% Pid = pid() +%% +%% Description: +%%-------------------------------------------------------------------------- +-spec stop(pid()) -> 'ok'. +stop(Pid) -> + Pid ! stop, + ok. + +%%%========================================================================= +%%% Internal functions +%%%========================================================================= +init(Options) -> + loop(progress(Options)). + +loop(Progress) -> + receive + {progress_report, Report} -> + NewProgress = report_progress(Report, Progress), + loop(NewProgress); + stop -> + ok + end. + +progress({CBModule, CBFunction, InitProgressTerm}) when is_atom(CBModule), + is_atom(CBFunction) -> + #progress{cb_module = CBModule, + cb_function = CBFunction, + init_progress_term = InitProgressTerm, + current_progress_term = InitProgressTerm}. + +report_progress({local_file, File}, Progress) -> + {ok, FileInfo} = file:read_file_info(File), + report_progress({file_size, FileInfo#file_info.size}, + Progress#progress{file = File}); + +report_progress({remote_file, File}, Progress) -> + report_progress({file_size, unknown}, Progress#progress{file = File}); + +report_progress(Size, #progress{file = File, + cb_module = CBModule, + cb_function = CBFunction, + current_progress_term = Term, + init_progress_term = InitTerm} = Progress) -> + + NewProgressTerm = CBModule:CBFunction(Term, File, Size), + + case Size of + {transfer_size, 0} -> + %% Transfer is compleat reset initial values + Progress#progress{current_progress_term = InitTerm, + file = undefined}; + _ -> + Progress#progress{current_progress_term = NewProgressTerm} + end. diff --git a/lib/ftp/src/ftp_response.erl b/lib/ftp/src/ftp_response.erl new file mode 100644 index 0000000000..d54d97dc91 --- /dev/null +++ b/lib/ftp/src/ftp_response.erl @@ -0,0 +1,203 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%% Description: This module impements handling of ftp server responses. + +-module(ftp_response). + +%% Internal API +-export([parse_lines/3, interpret/1, error_string/1]). + +-include("ftp_internal.hrl"). + +%% First group of reply code digits +-define(POS_PREL, 1). +-define(POS_COMPL, 2). +-define(POS_INTERM, 3). +-define(TRANS_NEG_COMPL, 4). +-define(PERM_NEG_COMPL, 5). +%% Second group of reply code digits +-define(SYNTAX,0). +-define(INFORMATION,1). +-define(CONNECTION,2). +-define(AUTH_ACC,3). +-define(UNSPEC,4). +-define(FILE_SYSTEM,5). + +%%%========================================================================= +%%% INTERNAL API +%%%========================================================================= + +%%-------------------------------------------------------------------------- +%% parse_lines(Data, AccLines, StatusCode) -> {ok, Lines} | +%% {continue, {Data, +%% AccLines, StatusCode}} +%% +%% Data = binary() - data recived on the control connection from the +%% ftp-server. +%% AccLines = [string()] +%% StatusCode = start | {byte(), byte(), byte()} | finish - +%% Indicates where in the parsing process we are. +%% start - (looking for the status code of the message) +%% {byte(), byte(), byte()} - status code found, now +%% looking for the last line indication. +%% finish - now on the last line. +%% Description: Parses a ftp control response message. +%% "A reply is defined to contain the 3-digit code, followed by Space +%% , followed by one line of text (where some maximum line length +%% has been specified), and terminated by the Telnet end-of-line +%% code (CRLF), or a so called multilined reply for example: +%% +%% 123-First line +%% Second line +%% 234 A line beginning with numbers +%% 123 The last line +%% +%% The user-process then simply needs to search for the second +%% occurrence of the same reply code, followed by (Space), at +%% the beginning of a line, and ignore all intermediary lines. If +%% an intermediary line begins with a 3-digit number, the Server +%% will pad the front to avoid confusion. +%%-------------------------------------------------------------------------- + +%% Make sure we received the first 4 bytes so we know how to parse +%% the FTP server response e.i. is the response composed of one +%% or multiple lines. +parse_lines(Bin, Lines, start) when size(Bin) < 4 -> + {continue, {Bin, Lines, start}}; +%% Multiple lines exist +parse_lines(<>, Lines, start) -> + parse_lines(Rest, [$-, C3, C2, C1 | Lines], {C1, C2, C3}); +%% Only one line exists +parse_lines(<>, Lines, start) -> + parse_lines(Bin, [?WHITE_SPACE, C3, C2, C1 | Lines], finish); + +%% Last line found +parse_lines(<>, Lines, {C1, C2, C3}) -> + parse_lines(Rest, [?WHITE_SPACE, C3, C2, C1, ?LF, ?CR | Lines], finish); +%% Potential end found wait for more data +parse_lines(<> = Bin, Lines, {C1, C2, C3}) -> + {continue, {Bin, Lines, {C1, C2, C3}}}; +%% Intermidate line begining with status code +parse_lines(<>, Lines, {C1, C2, C3}) -> + parse_lines(Rest, [C3, C2, C1, ?LF, ?CR | Lines], {C1, C2, C3}); + +%% Potential last line wait for more data +parse_lines(<> = Data, Lines, {C1, C2, _} = StatusCode) -> + {continue, {Data, Lines, StatusCode}}; +parse_lines(<> = Data, Lines, {C1, _, _} = StatusCode) -> + {continue, {Data, Lines, StatusCode}}; +parse_lines(<> = Data, Lines, {_,_,_} = StatusCode) -> + {continue, {Data, Lines, StatusCode}}; +parse_lines(<> = Data, Lines, {_,_,_} = StatusCode) -> + {continue, {Data, Lines, StatusCode}}; +parse_lines(<<>> = Data, Lines, {_,_,_} = StatusCode) -> + {continue, {Data, Lines, StatusCode}}; +%% Part of the multiple lines +parse_lines(<>, Lines, {_,_, _} = StatusCode) -> + parse_lines(Rest, [Octet | Lines], StatusCode); + +%% End of FTP server response found +parse_lines(<>, Lines, finish) -> + {ok, lists:reverse([?LF, ?CR | Lines]), <<>>}; +parse_lines(<>, Lines, finish) -> + {ok, lists:reverse([?LF, ?CR | Lines]), Rest}; + +%% Potential end found wait for more data +parse_lines(<> = Data, Lines, finish) -> + {continue, {Data, Lines, finish}}; +parse_lines(<<>> = Data, Lines, finish) -> + {continue, {Data, Lines, finish}}; +%% Part of last line +parse_lines(<>, Lines, finish) -> + parse_lines(Rest, [Octet | Lines], finish). + +%%-------------------------------------------------------------------------- +%% interpret(Lines) -> {Status, Text} +%% Lines = [byte(), byte(), byte() | Text] - ftp server response as +%% returned by parse_lines/3 +%% Stauts = atom() (see interpret_status/3) +%% Text = [string()] +%% +%% Description: Create nicer data to match on. +%%-------------------------------------------------------------------------- +interpret([Didgit1, Didgit2, Didgit3 | Data]) -> + Code1 = Didgit1 - $0, + Code2 = Didgit2 - $0, + Code3 = Didgit3 - $0, + {interpret_status(Code1, Code2, Code3), Data}. + +%%-------------------------------------------------------------------------- +%% error_string(Error) -> string() +%% Error = {error, term()} | term() +%% +%% Description: Translates error codes into strings intended for +%% human interpretation. +%%-------------------------------------------------------------------------- +error_string({error, Reason}) -> + error_string(Reason); + +error_string(echunk) -> "Synchronisation error during chunk sending."; +error_string(eclosed) -> "Session has been closed."; +error_string(econn) -> "Connection to remote server prematurely closed."; +error_string(eexists) ->"File or directory already exists."; +error_string(ehost) -> "Host not found, FTP server not found, " + "or connection rejected."; +error_string(elogin) -> "User not logged in."; +error_string(enotbinary) -> "Term is not a binary."; +error_string(epath) -> "No such file or directory, already exists, " + "or permission denied."; +error_string(etype) -> "No such type."; +error_string(euser) -> "User name or password not valid."; +error_string(etnospc) -> "Insufficient storage space in system."; +error_string(enofile) -> "No files found or file unavailable"; +error_string(epnospc) -> "Exceeded storage allocation " + "(for current directory or dataset)."; +error_string(efnamena) -> "File name not allowed."; +error_string(Reason) -> + lists:flatten(io_lib:format("Unknown error: ~w", [Reason])). + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== + +%% Positive Preleminary Reply +interpret_status(?POS_PREL,_,_) -> pos_prel; +%%FIXME ??? 3??? interpret_status(?POS_COMPL, ?AUTH_ACC, 3) -> tls_upgrade; +interpret_status(?POS_COMPL, ?AUTH_ACC, 4) -> tls_upgrade; +%% Positive Completion Reply +interpret_status(?POS_COMPL,_,_) -> pos_compl; +%% Positive Intermediate Reply nedd account +interpret_status(?POS_INTERM,?AUTH_ACC,2) -> pos_interm_acct; +%% Positive Intermediate Reply +interpret_status(?POS_INTERM,_,_) -> pos_interm; +%% No files found or file not available +interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,0) -> enofile; +%% No storage area no action taken +interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,2) -> etnospc; +%% Temporary Error, no action taken +interpret_status(?TRANS_NEG_COMPL,_,_) -> trans_neg_compl; +%% Permanent disk space error, the user shall not try again +interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,0) -> epath; +interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,2) -> epnospc; +interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,3) -> efnamena; +interpret_status(?PERM_NEG_COMPL,?AUTH_ACC,0) -> elogin; +interpret_status(?PERM_NEG_COMPL,_,_) -> perm_neg_compl. + diff --git a/lib/ftp/src/ftp_sup.erl b/lib/ftp/src/ftp_sup.erl new file mode 100644 index 0000000000..f30046802f --- /dev/null +++ b/lib/ftp/src/ftp_sup.erl @@ -0,0 +1,68 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +%%%------------------------------------------------------------------- +%% @doc ftp top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(ftp_sup). + +-behaviour(supervisor). + +%% API +-export([start_child/1, start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%==================================================================== +%% API functions +%%==================================================================== + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +start_child(Args) -> + supervisor:start_child(?MODULE, Args). + +%%==================================================================== +%% Supervisor callbacks +%%==================================================================== +init(_) -> + SupFlags = #{strategy => simple_one_for_one, + intensity => 0, + period => 3600}, + {ok, {SupFlags, child_specs()}}. + + +%%==================================================================== +%% Internal functions +%%==================================================================== +child_specs() -> + [#{id => undefined, + start => {ftp, start_link, []}, + restart => temporary, + shutdown => 4000, + type => worker, + modules => [ftp]}]. diff --git a/lib/ftp/test/Makefile b/lib/ftp/test/Makefile new file mode 100644 index 0000000000..38b85449f8 --- /dev/null +++ b/lib/ftp/test/Makefile @@ -0,0 +1,252 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-2018. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% +# +# +# For an outline of how this all_SUITE_data stuff works, see the +# make file ../../ssl/test/Makefile. +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN = $(FTP_VSN) + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +INCLUDES = -I. \ + -I$(ERL_TOP)/lib/ftp/src + +CP = cp + +ifeq ($(TESTROOT_DIR),) +TESTROOT_DIR = /ldisk/tests/$(USER)/ftp +endif + +ifeq ($(FTP_DATA_DIR),) +FTP_DATA_DIR = $(TESTROOT_DIR)/data_dir +endif + +ifeq ($(FTP_PRIV_DIR),) +FTP_PRIV_DIR = $(TESTROOT_DIR)/priv_dir +endif + +FTP_FLAGS = -Dftp__data_dir='"$(FTP_DATA_DIR)"' \ + -Dftp_priv_dir='"$(FTP_PRIV_DIR)"' + + +### +### test suite debug flags +### +ifeq ($(FTP_DEBUG_CLIENT),) + FTP_DEBUG_CLIENT = y +endif + +ifeq ($(FTP_DEBUG_CLIENT),) + FTP_FLAGS += -Dftp_debug_client +endif + +ifeq ($(FTP_TRACE_CLIENT),) + FTP_DEBUG_CLIENT = y +endif + +ifeq ($(FTP_TRACE_CLIENT),y) + FTP_FLAGS += -Dftp_trace_client +endif + +ifneq ($(FTP_DEBUG),) + FTP_DEBUG = s +endif + +ifeq ($(FTP_DEBUG),l) + FTP_FLAGS += -Dftp_log +endif + +ifeq ($(FTP_DEBUG),d) + FTP_FLAGS += -Dftp_debug -Dftp_log +endif + + +FTP_FLAGS += -pa ../ftp/ebin + +FTP_ROOT = ../ftp + +MODULES = \ + erl_make_certs \ + ftp_SUITE \ + ftp_format_SUITE \ + inets_test_lib + + +EBIN = . + +HRL_FILES = \ + ftp_internal.hrl \ + inets_test_lib.hrl + +ERL_FILES = $(MODULES:%=%.erl) + +SOURCE = $(ERL_FILES) $(HRL_FILES) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +FTP_SPECS = ftp.spec ftp_bench.spec +COVER_FILE = ftp.cover +FTP_FILES = ftp.config $(FTP_SPECS) + + +FTP_DATADIRS = ftp_SUITE_data + +DATADIRS = $(FTP_DATADIRS) + +EMAKEFILE = Emakefile +MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile) + +ifeq ($(MAKE_EMAKE),) +BUILDTARGET = $(TARGET_FILES) +RELTEST_FILES = $(COVER_FILE) $(FTP_SPECS) $(SOURCE) +else +BUILDTARGET = emakebuild +RELTEST_FILES = $(EMAKEFILE) $(COVER_FILE) $(FTP_SPECS) $(SOURCE) +endif + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- + +RELTESTSYSDIR = "$(RELEASE_PATH)/ftp_test" +RELTESTSYSALLDATADIR = $(RELTESTSYSDIR)/all_SUITE_data +RELTESTSYSBINDIR = $(RELTESTSYSALLDATADIR)/bin + + +# ---------------------------------------------------- +# FLAGS +# The path to the test_server ebin dir is needed when +# running the target "targets". +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += \ + $(INCLUDES) \ + $(FTP_FLAGS) + +# ---------------------------------------------------- +# Targets +# erl -sname kalle -pa ../ebin +# If you intend to run the test suite locally (private), then +# there is some requirements: +# 1) INETS_PRIV_DIR must be created +# ---------------------------------------------------- + +tests debug opt: $(BUILDTARGET) + +targets: $(TARGET_FILES) + +.PHONY: emakebuild + +emakebuild: $(EMAKEFILE) + +$(EMAKEFILE): + $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' | grep -v Warning > $(EMAKEFILE) + $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) | grep -v Warning >> $(EMAKEFILE) + +clean: + rm -f $(EMAKEFILE) + rm -f $(TARGET_FILES) + rm -f core *~ + +docs: + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) "$(RELSYSDIR)/test" + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) "$(RELSYSDIR)/test" + $(INSTALL_DATA) $(FTP_FILES) "$(RELSYSDIR)/test" + @for d in $(DATADIRS); do \ + echo "installing data dir $$d"; \ + if test -f $$d/TAR.exclude; then \ + echo $$d/TAR.exclude2 > $$d/TAR.exclude2; \ + cat $$d/TAR.exclude >> $$d/TAR.exclude2; \ + find $$d -name '*.contrib*' >> $$d/TAR.exclude2; \ + find $$d -name '*.keep*' >> $$d/TAR.exclude2; \ + find $$d -name '*.mkelem*' >> $$d/TAR.exclude2; \ + find $$d -name '*~' >> $$d/TAR.exclude2; \ + find $$d -name 'erl_crash.dump' >> $$d/TAR.exclude2; \ + find $$d -name 'core' >> $$d/TAR.exclude2; \ + find $$d -name '.cmake.state' >> $$d/TAR.exclude2; \ + tar cfX - $$d/TAR.exclude2 $$d | (cd "$(RELSYSDIR)/test"; tar xf -); \ + else \ + tar cf - $$d | (cd "$(RELSYSDIR)/test"; tar xf -); \ + fi; \ + done + +release_tests_spec: opt + $(INSTALL_DIR) $(RELTESTSYSDIR) + $(INSTALL_DATA) $(RELTEST_FILES) $(RELTESTSYSDIR) + chmod -R u+w $(RELTESTSYSDIR) + tar chf - $(DATADIRS) | (cd $(RELTESTSYSDIR); tar xf -) + $(INSTALL_DIR) $(RELTESTSYSALLDATADIR) + $(INSTALL_DIR) $(RELTESTSYSBINDIR) + chmod -R +x $(RELTESTSYSBINDIR) + $(INSTALL_DIR) $(RELTESTSYSALLDATADIR)/win32/lib + +release_docs_spec: + +info: + @echo "MAKE_EMAKE = $(MAKE_EMAKE)" + @echo "EMAKEFILE = $(EMAKEFILE)" + @echo "BUILDTARGET = $(BUILDTARGET)" + @echo "" + @echo "MODULES = $(MODULES)" + @echo "ERL_FILES = $(ERL_FILES)" + @echo "SOURCE = $(SOURCE)" + @echo "TARGET_FILES = $(TARGET_FILES)" + @echo "" + @echo "FTP_SPECS = $(FTP_SPECS)" + @echo "FTP_FILES = $(FTP_FILES)" + @echo "" + @echo "RELEASE_PATH = "$(RELEASE_PATH)"" + @echo "RELSYSDIR = "$(RELSYSDIR)"" + @echo "RELTESTSYSDIR = $(RELTESTSYSDIR)" + @echo "RELTESTSYSALLDATADIR = $(RELTESTSYSALLDATADIR)" + @echo "RELTESTSYSBINDIR = $(RELTESTSYSBINDIR)" + @echo "" + @echo "DATADIRS = $(DATADIRS)" + @echo "REL_DATADIRS = $(REL_DATADIRS)" + @echo "" + @echo "FTP_DATA_DIR = $(FTP_DATA_DIR)" + @echo "FTP_PRIV_DIR = $(FTP_PRIV_DIR)" + @echo "FTP_ROOT = $(FTP_ROOT)" + @echo "FTP_FLAGS = $(FTP_FLAGS)" + + diff --git a/lib/ftp/test/erl_make_certs.erl b/lib/ftp/test/erl_make_certs.erl new file mode 100644 index 0000000000..2db95825bc --- /dev/null +++ b/lib/ftp/test/erl_make_certs.erl @@ -0,0 +1,475 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% Create test certificates + +-module(erl_make_certs). +-include_lib("public_key/include/public_key.hrl"). + +-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]). +-compile(export_all). + +%%-------------------------------------------------------------------- +%% @doc Create and return a der encoded certificate +%% Option Default +%% ------------------------------------------------------- +%% digest sha1 +%% validity {date(), date() + week()} +%% version 3 +%% subject [] list of the following content +%% {name, Name} +%% {email, Email} +%% {city, City} +%% {state, State} +%% {org, Org} +%% {org_unit, OrgUnit} +%% {country, Country} +%% {serial, Serial} +%% {title, Title} +%% {dnQualifer, DnQ} +%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created) +%% (obs IssuerKey migth be {Key, Password} +%% key = KeyFile|KeyBin|rsa|dsa|ec Subject PublicKey rsa, dsa or ec generates key +%% +%% +%% (OBS: The generated keys are for testing only) +%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()} +%% @end +%%-------------------------------------------------------------------- + +make_cert(Opts) -> + SubjectPrivateKey = get_key(Opts), + {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts), + Cert = public_key:pkix_sign(TBSCert, IssuerKey), + true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok + {Cert, encode_key(SubjectPrivateKey)}. + +%%-------------------------------------------------------------------- +%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem" +%% @spec (::string(), ::string(), {Cert,Key}) -> ok +%% @end +%%-------------------------------------------------------------------- +write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) -> + ok = der_to_pem(filename:join(Dir, FileName ++ ".pem"), + [{'Certificate', Cert, not_encrypted}]), + ok = der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]). + +%%-------------------------------------------------------------------- +%% @doc Creates a rsa key (OBS: for testing only) +%% the size are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_rsa(Size) when is_integer(Size) -> + Key = gen_rsa2(Size), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Creates a dsa key (OBS: for testing only) +%% the sizes are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) -> + Key = gen_dsa2(LSize, NSize), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Creates a ec key (OBS: for testing only) +%% the sizes are in bytes +%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()} +%% @end +%%-------------------------------------------------------------------- +gen_ec(Curve) when is_atom(Curve) -> + Key = gen_ec2(Curve), + {Key, encode_key(Key)}. + +%%-------------------------------------------------------------------- +%% @doc Verifies cert signatures +%% @spec (::binary(), ::tuple()) -> ::boolean() +%% @end +%%-------------------------------------------------------------------- +verify_signature(DerEncodedCert, DerKey, _KeyParams) -> + Key = decode_key(DerKey), + case Key of + #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} -> + public_key:pkix_verify(DerEncodedCert, + #'RSAPublicKey'{modulus=Mod, publicExponent=Exp}); + #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> + public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}); + #'ECPrivateKey'{version = _Version, privateKey = _PrivKey, + parameters = Params, publicKey = {0, PubKey}} -> + public_key:pkix_verify(DerEncodedCert, {#'ECPoint'{point = PubKey}, Params}) + end. + +%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_key(Opts) -> + case proplists:get_value(key, Opts) of + undefined -> make_key(rsa, Opts); + rsa -> make_key(rsa, Opts); + dsa -> make_key(dsa, Opts); + ec -> make_key(ec, Opts); + Key -> + Password = proplists:get_value(password, Opts, no_passwd), + decode_key(Key, Password) + end. + +decode_key({Key, Pw}) -> + decode_key(Key, Pw); +decode_key(Key) -> + decode_key(Key, no_passwd). + + +decode_key(#'RSAPublicKey'{} = Key,_) -> + Key; +decode_key(#'RSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'DSAPrivateKey'{} = Key,_) -> + Key; +decode_key(#'ECPrivateKey'{} = Key,_) -> + Key; +decode_key(PemEntry = {_,_,_}, Pw) -> + public_key:pem_entry_decode(PemEntry, Pw); +decode_key(PemBin, Pw) -> + [KeyInfo] = public_key:pem_decode(PemBin), + decode_key(KeyInfo, Pw). + +encode_key(Key = #'RSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key), + {'RSAPrivateKey', Der, not_encrypted}; +encode_key(Key = #'DSAPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key), + {'DSAPrivateKey', Der, not_encrypted}; +encode_key(Key = #'ECPrivateKey'{}) -> + {ok, Der} = 'OTP-PUB-KEY':encode('ECPrivateKey', Key), + {'ECPrivateKey', Der, not_encrypted}. + +make_tbs(SubjectKey, Opts) -> + Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))), + + IssuerProp = proplists:get_value(issuer, Opts, true), + {Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey), + + {Algo, Parameters} = sign_algorithm(IssuerKey, Opts), + + SignAlgo = #'SignatureAlgorithm'{algorithm = Algo, + parameters = Parameters}, + Subject = case IssuerProp of + true -> %% Is a Root Ca + Issuer; + _ -> + subject(proplists:get_value(subject, Opts),false) + end, + + {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1, + signature = SignAlgo, + issuer = Issuer, + validity = validity(Opts), + subject = Subject, + subjectPublicKeyInfo = publickey(SubjectKey), + version = Version, + extensions = extensions(Opts) + }, IssuerKey}. + +issuer(true, Opts, SubjectKey) -> + %% Self signed + {subject(proplists:get_value(subject, Opts), true), SubjectKey}; +issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) -> + {issuer_der(Issuer), decode_key(IssuerKey)}; +issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) -> + {ok, [{cert, Cert, _}|_]} = pem_to_der(File), + {issuer_der(Cert), decode_key(IssuerKey)}. + +issuer_der(Issuer) -> + Decoded = public_key:pkix_decode_cert(Issuer, otp), + #'OTPCertificate'{tbsCertificate=Tbs} = Decoded, + #'OTPTBSCertificate'{subject=Subject} = Tbs, + Subject. + +subject(undefined, IsRootCA) -> + User = if IsRootCA -> "RootCA"; true -> os:getenv("USER", "test_user") end, + Opts = [{email, User ++ "@erlang.org"}, + {name, User}, + {city, "Stockholm"}, + {country, "SE"}, + {org, "erlang"}, + {org_unit, "testing dep"}], + subject(Opts); +subject(Opts, _) -> + subject(Opts). + +subject(SubjectOpts) when is_list(SubjectOpts) -> + Encode = fun(Opt) -> + {Type,Value} = subject_enc(Opt), + [#'AttributeTypeAndValue'{type=Type, value=Value}] + end, + {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}. + +%% Fill in the blanks +subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}}; +subject_enc({email, Email}) -> {?'id-emailAddress', Email}; +subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}}; +subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}}; +subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}}; +subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}}; +subject_enc({country, Country}) -> {?'id-at-countryName', Country}; +subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial}; +subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}}; +subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ}; +subject_enc(Other) -> Other. + + +extensions(Opts) -> + case proplists:get_value(extensions, Opts, []) of + false -> + asn1_NOVALUE; + Exts -> + lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)]) + end. + +default_extensions(Exts) -> + Def = [{key_usage,undefined}, + {subject_altname, undefined}, + {issuer_altname, undefined}, + {basic_constraints, default}, + {name_constraints, undefined}, + {policy_constraints, undefined}, + {ext_key_usage, undefined}, + {inhibit_any, undefined}, + {auth_key_id, undefined}, + {subject_key_id, undefined}, + {policy_mapping, undefined}], + Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end, + Exts ++ lists:foldl(Filter, Def, Exts). + +extension({_, undefined}) -> []; +extension({basic_constraints, Data}) -> + case Data of + default -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true}, + critical=true}; + false -> + []; + Len when is_integer(Len) -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len}, + critical=true}; + _ -> + #'Extension'{extnID = ?'id-ce-basicConstraints', + extnValue = Data} + end; +extension({Id, Data, Critical}) -> + #'Extension'{extnID = Id, extnValue = Data, critical = Critical}. + + +publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) -> + Public = #'RSAPublicKey'{modulus=N, publicExponent=E}, + Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = Public}; +publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa', + parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}; +publickey(#'ECPrivateKey'{version = _Version, + privateKey = _PrivKey, + parameters = Params, + publicKey = {0, PubKey}}) -> + Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, + #'OTPSubjectPublicKeyInfo'{algorithm = Algo, + subjectPublicKey = #'ECPoint'{point = PubKey}}. + +validity(Opts) -> + DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1), + DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7), + {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}), + Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end, + #'Validity'{notBefore={generalTime, Format(DefFrom)}, + notAfter ={generalTime, Format(DefTo)}}. + +sign_algorithm(#'RSAPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'sha1WithRSAEncryption'; + sha512 -> ?'sha512WithRSAEncryption'; + sha384 -> ?'sha384WithRSAEncryption'; + sha256 -> ?'sha256WithRSAEncryption'; + md5 -> ?'md5WithRSAEncryption'; + md2 -> ?'md2WithRSAEncryption' + end, + {Type, 'NULL'}; +sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> + {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}; +sign_algorithm(#'ECPrivateKey'{}, Opts) -> + Type = case proplists:get_value(digest, Opts, sha1) of + sha1 -> ?'ecdsa-with-SHA1'; + sha512 -> ?'ecdsa-with-SHA512'; + sha384 -> ?'ecdsa-with-SHA384'; + sha256 -> ?'ecdsa-with-SHA256' + end, + {Type, 'NULL'}. + +make_key(rsa, _Opts) -> + %% (OBS: for testing only) + gen_rsa2(64); +make_key(dsa, _Opts) -> + gen_dsa2(128, 20); %% Bytes i.e. {1024, 160} +make_key(ec, _Opts) -> + %% (OBS: for testing only) + gen_ec2(secp256k1). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% RSA key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53, + 47,43,41,37,31,29,23,19,17,13,11,7,5,3]). + +gen_rsa2(Size) -> + P = prime(Size), + Q = prime(Size), + N = P*Q, + Tot = (P - 1) * (Q - 1), + [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES), + {D1,D2} = extended_gcd(E, Tot), + D = erlang:max(D1,D2), + case D < E of + true -> + gen_rsa2(Size); + false -> + {Co1,Co2} = extended_gcd(Q, P), + Co = erlang:max(Co1,Co2), + #'RSAPrivateKey'{version = 'two-prime', + modulus = N, + publicExponent = E, + privateExponent = D, + prime1 = P, + prime2 = Q, + exponent1 = D rem (P-1), + exponent2 = D rem (Q-1), + coefficient = Co + } + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% DSA key generation (OBS: for testing only) +%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm +%% and the fips_186-3.pdf +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +gen_dsa2(LSize, NSize) -> + Q = prime(NSize), %% Choose N-bit prime Q + X0 = prime(LSize), + P0 = prime((LSize div 2) +1), + + %% Choose L-bit prime modulus P such that p-1 is a multiple of q. + case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of + error -> + gen_dsa2(LSize, NSize); + P -> + G = crypto:mod_pow(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q. + %% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used. + + X = prime(20), %% Choose x by some random method, where 0 < x < q. + Y = crypto:mod_pow(G, X, P), %% Calculate y = g^x mod p. + + #'DSAPrivateKey'{version=0, p = P, q = Q, + g = crypto:bytes_to_integer(G), y = crypto:bytes_to_integer(Y), x = X} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EC key generation (OBS: for testing only) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +gen_ec2(CurveId) -> + {PubKey, PrivKey} = crypto:generate_key(ecdh, CurveId), + + #'ECPrivateKey'{version = 1, + privateKey = binary_to_list(PrivKey), + parameters = {namedCurve, pubkey_cert_records:namedCurves(CurveId)}, + publicKey = {0, PubKey}}. + +%% See fips_186-3.pdf +dsa_search(T, P0, Q, Iter) when Iter > 0 -> + P = 2*T*Q*P0 + 1, + case is_prime(P, 50) of + true -> P; + false -> dsa_search(T+1, P0, Q, Iter-1) + end; +dsa_search(_,_,_,_) -> + error. + + +%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +prime(ByteSize) -> + Rand = odd_rand(ByteSize), + prime_odd(Rand, 0). + +prime_odd(Rand, N) -> + case is_prime(Rand, 50) of + true -> + Rand; + false -> + prime_odd(Rand+2, N+1) + end. + +%% see http://en.wikipedia.org/wiki/Fermat_primality_test +is_prime(_, 0) -> true; +is_prime(Candidate, Test) -> + CoPrime = odd_rand(10000, Candidate), + Result = crypto:mod_pow(CoPrime, Candidate, Candidate) , + is_prime(CoPrime, crypto:bytes_to_integer(Result), Candidate, Test). + +is_prime(CoPrime, CoPrime, Candidate, Test) -> + is_prime(Candidate, Test-1); +is_prime(_,_,_,_) -> + false. + +odd_rand(Size) -> + Min = 1 bsl (Size*8-1), + Max = (1 bsl (Size*8))-1, + odd_rand(Min, Max). + +odd_rand(Min,Max) -> + Rand = crypto:rand_uniform(Min,Max), + case Rand rem 2 of + 0 -> + Rand + 1; + _ -> + Rand + end. + +extended_gcd(A, B) -> + case A rem B of + 0 -> + {0, 1}; + N -> + {X, Y} = extended_gcd(B, N), + {Y, X-Y*(A div B)} + end. + +pem_to_der(File) -> + {ok, PemBin} = file:read_file(File), + public_key:pem_decode(PemBin). + +der_to_pem(File, Entries) -> + PemBin = public_key:pem_encode(Entries), + file:write_file(File, PemBin). + diff --git a/lib/ftp/test/ftp.config b/lib/ftp/test/ftp.config new file mode 100644 index 0000000000..6c9077594d --- /dev/null +++ b/lib/ftp/test/ftp.config @@ -0,0 +1 @@ +[{inets,[{services,[{httpd,"/ldisk/tests/bmk/inets/priv_dir/8099.conf"}]}]}]. diff --git a/lib/ftp/test/ftp.cover b/lib/ftp/test/ftp.cover new file mode 100644 index 0000000000..5b155991bc --- /dev/null +++ b/lib/ftp/test/ftp.cover @@ -0,0 +1,2 @@ +{incl_app,ftp,details}. + diff --git a/lib/ftp/test/ftp.spec b/lib/ftp/test/ftp.spec new file mode 100644 index 0000000000..faf1e532a8 --- /dev/null +++ b/lib/ftp/test/ftp.spec @@ -0,0 +1 @@ +{suites,"../ftp_test", all}. diff --git a/lib/ftp/test/ftp_SUITE.erl b/lib/ftp/test/ftp_SUITE.erl new file mode 100644 index 0000000000..3dfec01ba2 --- /dev/null +++ b/lib/ftp/test/ftp_SUITE.erl @@ -0,0 +1,1180 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +%% +%% ct:run("../inets_test", ftp_SUITE). +%% + +-module(ftp_SUITE). + +-include_lib("kernel/include/file.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include("inets_test_lib.hrl"). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-define(FTP_USER, "anonymous"). +-define(FTP_PASS(Cmnt), (fun({ok,__H}) -> "ftp_SUITE_"++Cmnt++"@" ++ __H; + (_) -> "ftp_SUITE_"++Cmnt++"@localhost" + end)(inet:gethostname()) + ). + +-define(BAD_HOST, "badhostname"). +-define(BAD_USER, "baduser"). +-define(BAD_DIR, "baddirectory"). + +-record(progress, { + current = 0, + total + }). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,20}}]. + +all() -> + [ + {group, ftp_passive}, + {group, ftp_active}, + {group, ftps_passive}, + {group, ftps_active}, + error_ehost, + clean_shutdown + ]. + +groups() -> + [ + {ftp_passive, [], ftp_tests()}, + {ftp_active, [], ftp_tests()}, + {ftps_passive, [], ftp_tests()}, + {ftps_active, [], ftp_tests()} + ]. + +ftp_tests()-> + [ + user, + bad_user, + pwd, + cd, + lcd, + ls, + nlist, + rename, + delete, + mkdir, + rmdir, + send, + send_3, + send_bin, + send_chunk, + append, + append_bin, + append_chunk, + recv, + recv_3, + recv_bin, + recv_bin_twice, + recv_chunk, + recv_chunk_twice, + recv_chunk_three_times, + type, + quote, + error_elogin, + progress_report_send, + progress_report_recv, + not_owner, + unexpected_call, + unexpected_cast, + unexpected_bang + ]. + +%%-------------------------------------------------------------------- + +%%% Config +%%% key meaning +%%% ................................................................ +%%% ftpservers list of servers to check if they are available +%%% The element is: +%%% {Name, % string(). The os command name +%%% Path, % string(). The os PATH syntax, e.g "/bin:/usr/bin" +%%% StartCommand, % fun()->{ok,start_result()} | {error,string()}. +%%% % The command to start the daemon with. +%%% ChkUp, % fun(start_result()) -> string(). Os command to check +%%% % if the server is running. [] if not running. +%%% % The string in string() is suitable for logging. +%%% StopCommand, % fun(start_result()) -> void(). The command to stop the daemon with. +%%% AugmentFun, % fun(config()) -> config() Adds two funs for transforming names of files +%%% % and directories to the form they are returned from this server +%%% ServerHost, % string(). Mostly "localhost" +%%% ServerPort % pos_integer() +%%% } +%%% + +-define(default_ftp_servers, + [{"vsftpd", + "/sbin:/usr/sbin:/usr/local/sbin", + fun(__CONF__, AbsName) -> + DataDir = proplists:get_value(data_dir,__CONF__), + ConfFile = filename:join(DataDir, "vsftpd.conf"), + PrivDir = proplists:get_value(priv_dir,__CONF__), + AnonRoot = PrivDir, + Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"), + " -oftpd_banner=erlang_otp_testing", + " -oanon_root=\"",AnonRoot,"\"", + " -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"", + " -orsa_private_key_file=\"",filename:join(DataDir,"server-key.pem"),"\"" + ], + Result = os:cmd(Cmd), + ct:log("Config file:~n~s~n~nServer start command:~n ~s~nResult:~n ~p", + [case file:read_file(ConfFile) of + {ok,X} -> X; + _ -> "" + end, + Cmd, Result + ]), + case Result of + [] -> {ok,'dont care'}; + [Msg] -> {error,Msg} + end + end, + fun(_StartResult) -> os:cmd("ps ax | grep erlang_otp_testing | grep -v grep") + end, + fun(_StartResult) -> os:cmd("kill `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`") + end, + fun(__CONF__) -> + AnonRoot = proplists:get_value(priv_dir,__CONF__), + [{id2ftp, fun(Id) -> filename:join(AnonRoot,Id) end}, + {id2ftp_result,fun(Id) -> filename:join(AnonRoot,Id) end} | __CONF__] + end, + "localhost", + 9999 + } + ] + ). + + +init_per_suite(Config) -> + case find_executable(Config) of + false -> + {skip, "No ftp server found"}; + {ok,Data} -> + TstDir = filename:join(proplists:get_value(priv_dir,Config), "test"), + file:make_dir(TstDir), + make_cert_files(dsa, rsa, "server-", proplists:get_value(data_dir,Config)), + start_ftpd([{test_dir,TstDir}, + {ftpd_data,Data} + | Config]) + end. + +end_per_suite(Config) -> + ps_ftpd(Config), + stop_ftpd(Config), + ps_ftpd(Config), + ok. + +%%-------------------------------------------------------------------- +init_per_group(Group, Config) when Group == ftps_active, + Group == ftps_passive -> + catch crypto:stop(), + try crypto:start() of + ok -> + Config + catch + _:_ -> + {skip, "Crypto did not start"} + end; + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, Config) -> + Config. + +%%-------------------------------------------------------------------- +init_per_testcase(Case, Config0) -> + Group = proplists:get_value(name, proplists:get_value(tc_group_properties,Config0)), + TLS = [{tls,[{reuse_sessions,true}]}], + ACTIVE = [{mode,active}], + PASSIVE = [{mode,passive}], + CaseOpts = case Case of + progress_report_send -> [{progress, {?MODULE,progress,#progress{}}}]; + progress_report_recv -> [{progress, {?MODULE,progress,#progress{}}}]; + _ -> [] + end, + ExtraOpts = [verbose | CaseOpts], + Config = + case Group of + ftp_active -> ftp__open(Config0, ACTIVE ++ ExtraOpts); + ftps_active -> ftp__open(Config0, TLS++ ACTIVE ++ ExtraOpts); + ftp_passive -> ftp__open(Config0, PASSIVE ++ ExtraOpts); + ftps_passive -> ftp__open(Config0, TLS++PASSIVE ++ ExtraOpts); + undefined -> Config0 + end, + case Case of + user -> Config; + bad_user -> Config; + error_elogin -> Config; + error_ehost -> Config; + clean_shutdown -> Config; + _ -> + Pid = proplists:get_value(ftp,Config), + ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS(atom_to_list(Group)++"-"++atom_to_list(Case)) ), + ok = ftp:cd(Pid, proplists:get_value(priv_dir,Config)), + Config + end. + + +end_per_testcase(user, _Config) -> ok; +end_per_testcase(bad_user, _Config) -> ok; +end_per_testcase(error_elogin, _Config) -> ok; +end_per_testcase(error_ehost, _Config) -> ok; +end_per_testcase(clean_shutdown, _Config) -> ok; +end_per_testcase(_Case, Config) -> + case proplists:get_value(tc_status,Config) of + ok -> ok; + _ -> + try ftp:latest_ctrl_response(proplists:get_value(ftp,Config)) + of + {ok,S} -> ct:log("***~n*** Latest ctrl channel response:~n*** ~p~n***",[S]) + catch + _:_ -> ok + end + end, + ftp__close(Config). + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +user() -> [ + {doc, "Open an ftp connection to a host, and logon as anonymous ftp," + " then logoff"}]. +user(Config) -> + Pid = proplists:get_value(ftp, Config), + ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS("")),% logon + ok = ftp:close(Pid), % logoff + {error,eclosed} = ftp:pwd(Pid), % check logoff result + ok. + +%%------------------------------------------------------------------------- +bad_user() -> + [{doc, "Open an ftp connection to a host, and logon with bad user."}]. +bad_user(Config) -> + Pid = proplists:get_value(ftp, Config), + {error, euser} = ftp:user(Pid, ?BAD_USER, ?FTP_PASS("")), + ok. + +%%------------------------------------------------------------------------- +pwd() -> + [{doc, "Test ftp:pwd/1 & ftp:lpwd/1"}]. +pwd(Config0) -> + Config = set_state([reset], Config0), + Pid = proplists:get_value(ftp, Config), + {ok, PWD} = ftp:pwd(Pid), + {ok, PathLpwd} = ftp:lpwd(Pid), + PWD = id2ftp_result("", Config), + PathLpwd = id2ftp_result("", Config). + +%%------------------------------------------------------------------------- +cd() -> + ["Open an ftp connection, log on as anonymous ftp, and cd to a" + "directory and to a non-existent directory."]. +cd(Config0) -> + Dir = "test", + Config = set_state([reset,{mkdir,Dir}], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:cd(Pid, id2ftp(Dir,Config)), + {ok, PWD} = ftp:pwd(Pid), + ExpectedPWD = id2ftp_result(Dir, Config), + PWD = ExpectedPWD, + {error, epath} = ftp:cd(Pid, ?BAD_DIR), + ok. + +%%------------------------------------------------------------------------- +lcd() -> + [{doc, "Test api function ftp:lcd/2"}]. +lcd(Config0) -> + Dir = "test", + Config = set_state([reset,{mkdir,Dir}], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:lcd(Pid, id2ftp(Dir,Config)), + {ok, PWD} = ftp:lpwd(Pid), + ExpectedPWD = id2ftp_result(Dir, Config), + PWD = ExpectedPWD, + {error, epath} = ftp:lcd(Pid, ?BAD_DIR). + +%%------------------------------------------------------------------------- +ls() -> + [{doc, "Open an ftp connection; ls the current directory, and the " + "\"test\" directory. We assume that ls never fails, since " + "it's output is meant to be read by humans. "}]. +ls(Config0) -> + Config = set_state([reset,{mkdir,"test"}], Config0), + Pid = proplists:get_value(ftp, Config), + {ok, _R1} = ftp:ls(Pid), + {ok, _R2} = ftp:ls(Pid, id2ftp("test",Config)), + %% neither nlist nor ls operates on a directory + %% they operate on a pathname, which *can* be a + %% directory, but can also be a filename or a group + %% of files (including wildcards). + case proplists:get_value(wildcard_support, Config) of + true -> + {ok, _R3} = ftp:ls(Pid, id2ftp("te*",Config)); + _ -> + ok + end. + +%%------------------------------------------------------------------------- +nlist() -> + [{doc,"Open an ftp connection; nlist the current directory, and the " + "\"test\" directory. Nlist does not behave consistenly over " + "operating systems. On some it is an error to have an empty " + "directory."}]. +nlist(Config0) -> + Config = set_state([reset,{mkdir,"test"}], Config0), + Pid = proplists:get_value(ftp, Config), + {ok, _R1} = ftp:nlist(Pid), + {ok, _R2} = ftp:nlist(Pid, id2ftp("test",Config)), + %% neither nlist nor ls operates on a directory + %% they operate on a pathname, which *can* be a + %% directory, but can also be a filename or a group + %% of files (including wildcards). + case proplists:get_value(wildcard_support, Config) of + true -> + {ok, _R3} = ftp:nlist(Pid, id2ftp("te*",Config)); + _ -> + ok + end. + +%%------------------------------------------------------------------------- +rename() -> + [{doc, "Rename a file."}]. +rename(Config0) -> + Contents = <<"ftp_SUITE test ...">>, + OldFile = "old.txt", + NewFile = "new.txt", + Config = set_state([reset,{mkfile,OldFile,Contents}], Config0), + Pid = proplists:get_value(ftp, Config), + + ok = ftp:rename(Pid, + id2ftp(OldFile,Config), + id2ftp(NewFile,Config)), + + true = (chk_file(NewFile,Contents,Config) + and chk_no_file([OldFile],Config)), + {error,epath} = ftp:rename(Pid, + id2ftp("non_existing_file",Config), + id2ftp(NewFile,Config)), + ok. + +%%------------------------------------------------------------------------- +send() -> + [{doc, "Transfer a file with ftp using send/2."}]. +send(Config0) -> + Contents = <<"ftp_SUITE test ...">>, + SrcDir = "data", + File = "file.txt", + Config = set_state([reset,{mkfile,[SrcDir,File],Contents}], Config0), + Pid = proplists:get_value(ftp, Config), + + chk_no_file([File],Config), + chk_file([SrcDir,File],Contents,Config), + + ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)), + ok = ftp:cd(Pid, id2ftp("",Config)), + ok = ftp:send(Pid, File), + chk_file(File, Contents, Config), + + {error,epath} = ftp:send(Pid, "non_existing_file"), + ok. + +%%------------------------------------------------------------------------- +send_3() -> + [{doc, "Transfer a file with ftp using send/3."}]. +send_3(Config0) -> + Contents = <<"ftp_SUITE test ...">>, + Dir = "incoming", + File = "file.txt", + RemoteFile = "remfile.txt", + Config = set_state([reset,{mkfile,File,Contents},{mkdir,Dir}], Config0), + Pid = proplists:get_value(ftp, Config), + + ok = ftp:cd(Pid, id2ftp(Dir,Config)), + ok = ftp:lcd(Pid, id2ftp("",Config)), + ok = ftp:send(Pid, File, RemoteFile), + chk_file([Dir,RemoteFile], Contents, Config), + + {error,epath} = ftp:send(Pid, "non_existing_file", RemoteFile), + ok. + +%%------------------------------------------------------------------------- +send_bin() -> + [{doc, "Send a binary."}]. +send_bin(Config0) -> + BinContents = <<"ftp_SUITE test ...">>, + File = "file.txt", + Config = set_state([reset], Config0), + Pid = proplists:get_value(ftp, Config), + {error, enotbinary} = ftp:send_bin(Pid, "some string", id2ftp(File,Config)), + ok = ftp:send_bin(Pid, BinContents, id2ftp(File,Config)), + chk_file(File, BinContents, Config), + {error, efnamena} = ftp:send_bin(Pid, BinContents, "/nothere"), + ok. + +%%------------------------------------------------------------------------- +send_chunk() -> + [{doc, "Send a binary using chunks."}]. +send_chunk(Config0) -> + Contents1 = <<"1: ftp_SUITE test ...">>, + Contents2 = <<"2: ftp_SUITE test ...">>, + File = "file.txt", + Config = set_state([reset,{mkdir,"incoming"}], Config0), + Pid = proplists:get_value(ftp, Config), + + ok = ftp:send_chunk_start(Pid, id2ftp(File,Config)), + {error, echunk} = ftp:send_chunk_start(Pid, id2ftp(File,Config)), + {error, echunk} = ftp:cd(Pid, "incoming"), + {error, enotbinary} = ftp:send_chunk(Pid, "some string"), + ok = ftp:send_chunk(Pid, Contents1), + ok = ftp:send_chunk(Pid, Contents2), + ok = ftp:send_chunk_end(Pid), + chk_file(File, <>, Config), + + {error, echunk} = ftp:send_chunk(Pid, Contents1), + {error, echunk} = ftp:send_chunk_end(Pid), + {error, efnamena} = ftp:send_chunk_start(Pid, "/"), + ok. + +%%------------------------------------------------------------------------- +delete() -> + [{doc, "Delete a file."}]. +delete(Config0) -> + Contents = <<"ftp_SUITE test ...">>, + File = "file.txt", + Config = set_state([reset,{mkfile,File,Contents}], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:delete(Pid, id2ftp(File,Config)), + chk_no_file([File], Config), + {error,epath} = ftp:delete(Pid, id2ftp(File,Config)), + ok. + +%%------------------------------------------------------------------------- +mkdir() -> + [{doc, "Make a remote directory."}]. +mkdir(Config0) -> + NewDir = "new_dir", + Config = set_state([reset], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:mkdir(Pid, id2ftp(NewDir,Config)), + chk_dir([NewDir], Config), + {error,epath} = ftp:mkdir(Pid, id2ftp(NewDir,Config)), + ok. + +%%------------------------------------------------------------------------- +rmdir() -> + [{doc, "Remove a directory."}]. +rmdir(Config0) -> + Dir = "dir", + Config = set_state([reset,{mkdir,Dir}], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:rmdir(Pid, id2ftp(Dir,Config)), + chk_no_dir([Dir], Config), + {error,epath} = ftp:rmdir(Pid, id2ftp(Dir,Config)), + ok. + +%%------------------------------------------------------------------------- +append() -> + [{doc, "Append a local file twice to a remote file"}]. +append(Config0) -> + SrcFile = "f_src.txt", + DstFile = "f_dst.txt", + Contents = <<"ftp_SUITE test ...">>, + Config = set_state([reset,{mkfile,SrcFile,Contents}], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)), + ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)), + chk_file(DstFile, <>, Config), + {error,epath} = ftp:append(Pid, id2ftp("non_existing_file",Config), id2ftp(DstFile,Config)), + ok. + +%%------------------------------------------------------------------------- +append_bin() -> + [{doc, "Append a local file twice to a remote file using append_bin"}]. +append_bin(Config0) -> + DstFile = "f_dst.txt", + Contents = <<"ftp_SUITE test ...">>, + Config = set_state([reset], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:append_bin(Pid, Contents, id2ftp(DstFile,Config)), + ok = ftp:append_bin(Pid, Contents, id2ftp(DstFile,Config)), + chk_file(DstFile, <>, Config). + +%%------------------------------------------------------------------------- +append_chunk() -> + [{doc, "Append chunks."}]. +append_chunk(Config0) -> + File = "f_dst.txt", + Contents = [<<"ER">>,<<"LE">>,<<"RL">>], + Config = set_state([reset], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:append_chunk_start(Pid, id2ftp(File,Config)), + {error, enotbinary} = ftp:append_chunk(Pid, binary_to_list(lists:nth(1,Contents))), + ok = ftp:append_chunk(Pid,lists:nth(1,Contents)), + ok = ftp:append_chunk(Pid,lists:nth(2,Contents)), + ok = ftp:append_chunk(Pid,lists:nth(3,Contents)), + ok = ftp:append_chunk_end(Pid), + chk_file(File, <<"ERLERL">>, Config). + +%%------------------------------------------------------------------------- +recv() -> + [{doc, "Receive a file using recv/2"}]. +recv(Config0) -> + File1 = "f_dst1.txt", + File2 = "f_dst2.txt", + SrcDir = "a_dir", + Contents1 = <<"1 ftp_SUITE test ...">>, + Contents2 = <<"2 ftp_SUITE test ...">>, + Config = set_state([reset, {mkfile,[SrcDir,File1],Contents1}, {mkfile,[SrcDir,File2],Contents2}], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:cd(Pid, id2ftp(SrcDir,Config)), + ok = ftp:lcd(Pid, id2ftp("",Config)), + ok = ftp:recv(Pid, File1), + chk_file(File1, Contents1, Config), + ok = ftp:recv(Pid, File2), + chk_file(File2, Contents2, Config), + {error,epath} = ftp:recv(Pid, "non_existing_file"), + ok. + +%%------------------------------------------------------------------------- +recv_3() -> + [{doc,"Receive a file using recv/3"}]. +recv_3(Config0) -> + DstFile = "f_src.txt", + SrcFile = "f_dst.txt", + Contents = <<"ftp_SUITE test ...">>, + Config = set_state([reset, {mkfile,SrcFile,Contents}], Config0), + Pid = proplists:get_value(ftp, Config), + ok = ftp:cd(Pid, id2ftp("",Config)), + ok = ftp:recv(Pid, SrcFile, id2abs(DstFile,Config)), + chk_file(DstFile, Contents, Config). + +%%------------------------------------------------------------------------- +recv_bin() -> + [{doc, "Receive a file as a binary."}]. +recv_bin(Config0) -> + File = "f_dst.txt", + Contents = <<"ftp_SUITE test ...">>, + Config = set_state([reset, {mkfile,File,Contents}], Config0), + Pid = proplists:get_value(ftp, Config), + {ok,Received} = ftp:recv_bin(Pid, id2ftp(File,Config)), + find_diff(Received, Contents), + {error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)), + ok. + +%%------------------------------------------------------------------------- +recv_bin_twice() -> + [{doc, "Receive two files as a binaries."}]. +recv_bin_twice(Config0) -> + File1 = "f_dst1.txt", + File2 = "f_dst2.txt", + Contents1 = <<"1 ftp_SUITE test ...">>, + Contents2 = <<"2 ftp_SUITE test ...">>, + Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}], Config0), + ct:log("First transfer",[]), + Pid = proplists:get_value(ftp, Config), + {ok,Received1} = ftp:recv_bin(Pid, id2ftp(File1,Config)), + find_diff(Received1, Contents1), + ct:log("Second transfer",[]), + {ok,Received2} = ftp:recv_bin(Pid, id2ftp(File2,Config)), + find_diff(Received2, Contents2), + ct:log("Transfers ready!",[]), + {error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)), + ok. +%%------------------------------------------------------------------------- +recv_chunk() -> + [{doc, "Receive a file using chunk-wise."}]. +recv_chunk(Config0) -> + File = "big_file.txt", + Contents = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ), + Config = set_state([reset, {mkfile,File,Contents}], Config0), + Pid = proplists:get_value(ftp, Config), + {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>), + ok = ftp:recv_chunk_start(Pid, id2ftp(File,Config)), + {ok, ReceivedContents, _Ncunks} = recv_chunk(Pid, <<>>), + find_diff(ReceivedContents, Contents). + +recv_chunk_twice() -> + [{doc, "Receive two files using chunk-wise."}]. +recv_chunk_twice(Config0) -> + File1 = "big_file1.txt", + File2 = "big_file2.txt", + Contents1 = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ), + Contents2 = crypto:strong_rand_bytes(1200), + Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}], Config0), + Pid = proplists:get_value(ftp, Config), + {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>), + ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)), + {ok, ReceivedContents1, _Ncunks1} = recv_chunk(Pid, <<>>), + ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)), + {ok, ReceivedContents2, _Ncunks2} = recv_chunk(Pid, <<>>), + find_diff(ReceivedContents1, Contents1), + find_diff(ReceivedContents2, Contents2). + +recv_chunk_three_times() -> + [{doc, "Receive two files using chunk-wise."}, + {timetrap,{seconds,120}}]. +recv_chunk_three_times(Config0) -> + File1 = "big_file1.txt", + File2 = "big_file2.txt", + File3 = "big_file3.txt", + Contents1 = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ), + Contents2 = crypto:strong_rand_bytes(1200), + Contents3 = list_to_binary( lists:duplicate(1000, lists:seq(255,0,-1)) ), + + Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}, {mkfile,File3,Contents3}], Config0), + Pid = proplists:get_value(ftp, Config), + {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>), + + ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)), + {ok, ReceivedContents1, Nchunks1} = recv_chunk(Pid, <<>>), + + ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)), + {ok, ReceivedContents2, _Nchunks2} = recv_chunk(Pid, <<>>), + + ok = ftp:recv_chunk_start(Pid, id2ftp(File3,Config)), + {ok, ReceivedContents3, _Nchunks3} = recv_chunk(Pid, <<>>, 10000, 0, Nchunks1), + + find_diff(ReceivedContents1, Contents1), + find_diff(ReceivedContents2, Contents2), + find_diff(ReceivedContents3, Contents3). + + + +recv_chunk(Pid, Acc) -> + recv_chunk(Pid, Acc, 0, 0, undefined). + + + +%% ExpectNchunks :: integer() | undefined +recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) when N+1 < ExpectNchunks -> + %% for all I in integer(), I < undefined + recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks); + +recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) -> + %% N >= ExpectNchunks-1 + timer:sleep(DelayMilliSec), + recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks). + + +recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks) -> + ct:log("Call ftp:recv_chunk",[]), + case ftp:recv_chunk(Pid) of + ok -> {ok, Acc, N}; + {ok, Bin} -> recv_chunk(Pid, <>, DelayMilliSec, N+1, ExpectNchunks); + Error -> {Error, N} + end. + +%%------------------------------------------------------------------------- +type() -> + [{doc,"Test that we can change btween ASCCI and binary transfer mode"}]. +type(Config) -> + Pid = proplists:get_value(ftp, Config), + ok = ftp:type(Pid, ascii), + ok = ftp:type(Pid, binary), + ok = ftp:type(Pid, ascii), + {error, etype} = ftp:type(Pid, foobar). + +%%------------------------------------------------------------------------- +quote(Config) -> + Pid = proplists:get_value(ftp, Config), + ["257 \""++_Rest] = ftp:quote(Pid, "pwd"), %% 257 + [_| _] = ftp:quote(Pid, "help"), + %% This negativ test causes some ftp servers to hang. This test + %% is not important for the client, so we skip it for now. + %%["425 Can't build data connection: Connection refused."] + %% = ftp:quote(Pid, "list"), + ok. + +%%------------------------------------------------------------------------- +progress_report_send() -> + [{doc, "Test the option progress for ftp:send/[2,3]"}]. +progress_report_send(Config) when is_list(Config) -> + ReportPid = + spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]), + send(Config), + receive + {ReportPid, ok} -> + ok + end. + +%%------------------------------------------------------------------------- +progress_report_recv() -> + [{doc, "Test the option progress for ftp:recv/[2,3]"}]. +progress_report_recv(Config) when is_list(Config) -> + ReportPid = + spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]), + recv(Config), + receive + {ReportPid, ok} -> + ok + end. + +%%------------------------------------------------------------------------- + +not_owner() -> + [{doc, "Test what happens if a process that not owns the connection tries " + "to use it"}]. +not_owner(Config) when is_list(Config) -> + Pid = proplists:get_value(ftp, Config), + + Parent = self(), + OtherPid = spawn_link( + fun() -> + {error, not_connection_owner} = ftp:pwd(Pid), + ftp:close(Pid), + Parent ! {self(), ok} + end), + receive + {OtherPid, ok} -> + {ok, _} = ftp:pwd(Pid) + end. + + +%%------------------------------------------------------------------------- + + +unexpected_call()-> + [{doc, "Test that behaviour of the ftp process if the api is abused"}]. +unexpected_call(Config) when is_list(Config) -> + Flag = process_flag(trap_exit, true), + Pid = proplists:get_value(ftp, Config), + + %% Serious programming fault, connetion will be shut down + case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of + {error, {connection_terminated, 'API_violation'}} -> + ok; + Unexpected1 -> + exit({unexpected_result, Unexpected1}) + end, + ct:sleep(500), + undefined = process_info(Pid, status), + process_flag(trap_exit, Flag). +%%------------------------------------------------------------------------- + +unexpected_cast()-> + [{doc, "Test that behaviour of the ftp process if the api is abused"}]. +unexpected_cast(Config) when is_list(Config) -> + Flag = process_flag(trap_exit, true), + Pid = proplists:get_value(ftp, Config), + %% Serious programming fault, connetion will be shut down + gen_server:cast(Pid, {self(), foobar, 10}), + ct:sleep(500), + undefined = process_info(Pid, status), + process_flag(trap_exit, Flag). +%%------------------------------------------------------------------------- + +unexpected_bang()-> + [{doc, "Test that connection ignores unexpected bang"}]. +unexpected_bang(Config) when is_list(Config) -> + Flag = process_flag(trap_exit, true), + Pid = proplists:get_value(ftp, Config), + %% Could be an innocent misstake the connection lives. + Pid ! foobar, + ct:sleep(500), + {status, _} = process_info(Pid, status), + process_flag(trap_exit, Flag). + +%%------------------------------------------------------------------------- + +clean_shutdown() -> + [{doc, "Test that owning process that exits with reason " + "'shutdown' does not cause an error message. OTP 6035"}]. + +clean_shutdown(Config) -> + Parent = self(), + HelperPid = spawn( + fun() -> + ftp__open(Config, [verbose]), + Parent ! ok, + receive + nothing -> ok + end + end), + receive + ok -> + PrivDir = proplists:get_value(priv_dir, Config), + LogFile = filename:join([PrivDir,"ticket_6035.log"]), + error_logger:logfile({open, LogFile}), + exit(HelperPid, shutdown), + timer:sleep(2000), + error_logger:logfile(close), + case is_error_report_6035(LogFile) of + true -> ok; + false -> {fail, "Bad logfile"} + end + end. + +%%%---------------------------------------------------------------- +%%% Error codes not tested elsewhere + +error_elogin(Config0) -> + Dir = "test", + OldFile = "old.txt", + NewFile = "new.txt", + SrcDir = "data", + File = "file.txt", + Config = set_state([reset, + {mkdir,Dir}, + {mkfile,OldFile,<<"Contents..">>}, + {mkfile,[SrcDir,File],<<"Contents..">>}], Config0), + + Pid = proplists:get_value(ftp, Config), + ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)), + {error,elogin} = ftp:send(Pid, File), + ok = ftp:lcd(Pid, id2ftp("",Config)), + {error,elogin} = ftp:pwd(Pid), + {error,elogin} = ftp:cd(Pid, id2ftp(Dir,Config)), + {error,elogin} = ftp:rename(Pid, + id2ftp(OldFile,Config), + id2ftp(NewFile,Config)), + ok. + +error_ehost(_Config) -> + {error, ehost} = ftp:open("nohost.nodomain"), + ok. + +%%-------------------------------------------------------------------- +%% Internal functions ----------------------------------------------- +%%-------------------------------------------------------------------- + +make_cert_files(Alg1, Alg2, Prefix, Dir) -> + CaInfo = {CaCert,_} = erl_make_certs:make_cert([{key,Alg1}]), + {Cert,CertKey} = erl_make_certs:make_cert([{key,Alg2},{issuer,CaInfo}]), + CaCertFile = filename:join(Dir, Prefix++"cacerts.pem"), + CertFile = filename:join(Dir, Prefix++"cert.pem"), + KeyFile = filename:join(Dir, Prefix++"key.pem"), + der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]), + der_to_pem(CertFile, [{'Certificate', Cert, not_encrypted}]), + der_to_pem(KeyFile, [CertKey]), + ok. + +der_to_pem(File, Entries) -> + PemBin = public_key:pem_encode(Entries), + file:write_file(File, PemBin). + +%%-------------------------------------------------------------------- +chk_file(Path=[C|_], ExpectedContents, Config) when 0 + chk_file([Path], ExpectedContents, Config); + +chk_file(PathList, ExpectedContents, Config) -> + Path = filename:join(PathList), + AbsPath = id2abs(Path,Config), + case file:read_file(AbsPath) of + {ok,ExpectedContents} -> + true; + {ok,ReadContents} -> + {error,{diff,Pos,RC,LC}} = find_diff(ReadContents, ExpectedContents, 1), + ct:log("Bad contents of ~p.~nGot:~n~p~nExpected:~n~p~nDiff at pos ~p ~nRead: ~p~nExp : ~p", + [AbsPath,ReadContents,ExpectedContents,Pos,RC,LC]), + ct:fail("Bad contents of ~p", [Path]); + {error,Error} -> + try begin + {ok,CWD} = file:get_cwd(), + ct:log("file:get_cwd()=~p~nfiles:~n~p",[CWD,file:list_dir(CWD)]) + end + of _ -> ok + catch _:_ ->ok + end, + ct:fail("Error reading ~p: ~p",[Path,Error]) + end. + + +chk_no_file(Path=[C|_], Config) when 0 + chk_no_file([Path], Config); + +chk_no_file(PathList, Config) -> + Path = filename:join(PathList), + AbsPath = id2abs(Path,Config), + case file:read_file(AbsPath) of + {error,enoent} -> + true; + {ok,Contents} -> + ct:log("File ~p exists although it shouldn't. Contents:~n~p", + [AbsPath,Contents]), + ct:fail("File exists: ~p", [Path]); + {error,Error} -> + ct:fail("Unexpected error reading ~p: ~p",[Path,Error]) + end. + + +chk_dir(Path=[C|_], Config) when 0 + chk_dir([Path], Config); + +chk_dir(PathList, Config) -> + Path = filename:join(PathList), + AbsPath = id2abs(Path,Config), + case file:read_file_info(AbsPath) of + {ok, #file_info{type=directory}} -> + true; + {ok, #file_info{type=Type}} -> + ct:fail("Expected dir ~p is a ~p",[Path,Type]); + {error,Error} -> + ct:fail("Expected dir ~p: ~p",[Path,Error]) + end. + +chk_no_dir(PathList, Config) -> + Path = filename:join(PathList), + AbsPath = id2abs(Path,Config), + case file:read_file_info(AbsPath) of + {error,enoent} -> + true; + {ok, #file_info{type=directory}} -> + ct:fail("Dir ~p erroneously exists",[Path]); + {ok, #file_info{type=Type}} -> + ct:fail("~p ~p erroneously exists",[Type,Path]); + {error,Error} -> + ct:fail("Unexpected error for ~p: ~p",[Path,Error]) + end. + +%%-------------------------------------------------------------------- +find_executable(Config) -> + search_executable(proplists:get_value(ftpservers, Config, ?default_ftp_servers)). + + +search_executable([{Name,Paths,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}|Srvrs]) -> + case os_find(Name,Paths) of + false -> + ct:log("~p not found",[Name]), + search_executable(Srvrs); + AbsName -> + ct:comment("Found ~p",[AbsName]), + {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}} + end; +search_executable([]) -> + false. + + +os_find(Name, Paths) -> + case os:find_executable(Name, Paths) of + false -> os:find_executable(Name); + AbsName -> AbsName + end. + +%%%---------------------------------------------------------------- +start_ftpd(Config0) -> + {AbsName,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} = + proplists:get_value(ftpd_data, Config0), + case StartCmd(Config0, AbsName) of + {ok,StartResult} -> + Config = [{ftpd_host,Host}, + {ftpd_port,Port}, + {ftpd_start_result,StartResult} | ConfigRewrite(Config0)], + try + ftp__close(ftp__open(Config,[verbose])) + of + Config1 when is_list(Config1) -> + ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]), + Config + catch + Class:Exception -> + ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p", + [AbsName,Host,Port,Class,Exception]), + {skip, [AbsName," started but unusuable"]} + end; + {error,Msg} -> + {skip, [AbsName," not started: ",Msg]} + end. + +stop_ftpd(Config) -> + {_Name,_StartCmd,_ChkUp,StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), + StopCommand(proplists:get_value(ftpd_start_result,Config)). + +ps_ftpd(Config) -> + {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), + ct:log( ChkUp(proplists:get_value(ftpd_start_result,Config)) ). + + +ftpd_running(Config) -> + {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), + ChkUp(proplists:get_value(ftpd_start_result,Config)). + +ftp__open(Config, Options) -> + Host = proplists:get_value(ftpd_host,Config), + Port = proplists:get_value(ftpd_port,Config), + ct:log("Host=~p, Port=~p",[Host,Port]), + {ok,Pid} = ftp:open(Host, [{port,Port} | Options]), + [{ftp,Pid}|Config]. + +ftp__close(Config) -> + ok = ftp:close(proplists:get_value(ftp,Config)), + Config. + +split(Cs) -> string:tokens(Cs, "\r\n"). + +find_diff(Bin1, Bin2) -> + case find_diff(Bin1, Bin2, 1) of + {error, {diff,Pos,RC,LC}} -> + ct:log("Contents differ at position ~p.~nOp1: ~p~nOp2: ~p",[Pos,RC,LC]), + ct:fail("Contents differ at pos ~p",[Pos]); + Other -> + Other + end. + +find_diff(A, A, _) -> true; +find_diff(<>, <>, Pos) -> find_diff(T1, T2, Pos+1); +find_diff(RC, LC, Pos) -> {error, {diff, Pos, RC, LC}}. + +set_state(Ops, Config) when is_list(Ops) -> lists:foldl(fun set_state/2, Config, Ops); + +set_state(reset, Config) -> + rm('*', id2abs("",Config)), + PrivDir = proplists:get_value(priv_dir,Config), + file:set_cwd(PrivDir), + ftp:lcd(proplists:get_value(ftp,Config),PrivDir), + set_state({mkdir,""},Config); +set_state({mkdir,Id}, Config) -> + Abs = id2abs(Id, Config), + mk_path(Abs), + file:make_dir(Abs), + Config; +set_state({mkfile,Id,Contents}, Config) -> + Abs = id2abs(Id, Config), + mk_path(Abs), + ok = file:write_file(Abs, Contents), + Config. + +mk_path(Abs) -> lists:foldl(fun mk_path/2, [], filename:split(filename:dirname(Abs))). + +mk_path(F, Pfx) -> + case file:read_file_info(AbsName=filename:join(Pfx,F)) of + {ok,#file_info{type=directory}} -> + AbsName; + {error,eexist} -> + AbsName; + {error,enoent} -> + ok = file:make_dir(AbsName), + AbsName + end. + +rm('*', Pfx) -> + {ok,Fs} = file:list_dir(Pfx), + lists:foreach(fun(F) -> rm(F, Pfx) end, Fs); +rm(F, Pfx) -> + case file:read_file_info(AbsName=filename:join(Pfx,F)) of + {ok,#file_info{type=directory}} -> + {ok,Fs} = file:list_dir(AbsName), + lists:foreach(fun(F1) -> rm(F1,AbsName) end, Fs), + ok = file:del_dir(AbsName); + + {ok,#file_info{type=regular}} -> + ok = file:delete(AbsName); + + {error,enoent} -> + ok + end. + +id2abs(Id, Conf) -> filename:join(proplists:get_value(priv_dir,Conf),ids(Id)). +id2ftp(Id, Conf) -> (proplists:get_value(id2ftp,Conf))(ids(Id)). +id2ftp_result(Id, Conf) -> (proplists:get_value(id2ftp_result,Conf))(ids(Id)). + +ids([[_|_]|_]=Ids) -> filename:join(Ids); +ids(Id) -> Id. + + +is_expected_absName(Id, File, Conf) -> File = (proplists:get_value(id2abs,Conf))(Id). +is_expected_ftpInName(Id, File, Conf) -> File = (proplists:get_value(id2ftp,Conf))(Id). +is_expected_ftpOutName(Id, File, Conf) -> File = (proplists:get_value(id2ftp_result,Conf))(Id). + + +%%%---------------------------------------------------------------- +%%% Help functions for the option '{progress,Progress}' +%%% + +%%%---------------- +%%% Callback: + +progress(#progress{} = P, _File, {file_size, Total} = M) -> + ct:pal("Progress: ~p",[M]), + progress_report_receiver ! start, + P#progress{total = Total}; + +progress(#progress{current = Current} = P, _File, {transfer_size, 0} = M) -> + ct:pal("Progress: ~p",[M]), + progress_report_receiver ! finish, + case P#progress.total of + unknown -> P; + Current -> P; + Total -> ct:fail({error, {progress, {total,Total}, {current,Current}}}), + P + end; + +progress(#progress{current = Current} = P, _File, {transfer_size, Size} = M) -> + ct:pal("Progress: ~p",[M]), + progress_report_receiver ! update, + P#progress{current = Current + Size}; + +progress(P, _File, M) -> + ct:pal("Progress **** Strange: ~p",[M]), + P. + + +%%%---------------- +%%% Help process that counts the files transferred: + +progress_report_receiver_init(Parent, N) -> + register(progress_report_receiver, self()), + progress_report_receiver_expect_N_files(Parent, N). + +progress_report_receiver_expect_N_files(_Parent, 0) -> + ct:pal("progress_report got all files!", []); +progress_report_receiver_expect_N_files(Parent, N) -> + ct:pal("progress_report expects ~p more files",[N]), + receive + start -> ok + end, + progress_report_receiver_loop(Parent, N-1). + + +progress_report_receiver_loop(Parent, N) -> + ct:pal("progress_report expect update | finish. N = ~p",[N]), + receive + update -> + ct:pal("progress_report got update",[]), + progress_report_receiver_loop(Parent, N); + finish -> + ct:pal("progress_report got finish, send ~p to ~p",[{self(),ok}, Parent]), + Parent ! {self(), ok}, + progress_report_receiver_expect_N_files(Parent, N) + end. + +%%%---------------------------------------------------------------- +%%% Help functions for bug OTP-6035 + +is_error_report_6035(LogFile) -> + case file:read_file(LogFile) of + {ok, Bin} -> + nomatch =/= binary:match(Bin, <<"=ERROR REPORT====">>); + _ -> + false + end. + diff --git a/lib/ftp/test/ftp_SUITE_data/ftpd_hosts.skel b/lib/ftp/test/ftp_SUITE_data/ftpd_hosts.skel new file mode 100644 index 0000000000..75096ce687 --- /dev/null +++ b/lib/ftp/test/ftp_SUITE_data/ftpd_hosts.skel @@ -0,0 +1,18 @@ +%% Add a host name in the appropriate list +%% Each "platform" contains a list of hostnames (a string) that can +%% be used for testing the ftp client. +%% The definition below are an example!! +[{solaris8_sparc, ["solaris8_sparc_dummy1", "solaris8_sparc_dummy2"]}, + {solaris9_sparc, ["solaris9_sparc_dummy1"]}, + {solaris10_sparc, ["solaris10_sparc_dummy1"]}, + {solaris10_x86, ["solaris10_x86_dummy1", "solaris10_x86_dummy2"]}, + {linux_x86, ["linux_x86_dummy1", "linux_x86_dummy2"]}, + {linux_ppc, ["linux_ppc_dummy1"]}, + {macosx_ppc, ["macosx_ppc_dummy1"]}, + {macosx_x86, ["macosx_x86_dummy1", "macosx_x86_dummy2"]}, + {openbsd_x86, []}, + {freebsd_x86, ["freebsd_x86_dummy1"]}, + {netbsd_x86, []}, + {windows_xp, []}, + {windows_2003_server, ["win2003_dummy1"]}, + {ticket_test, ["solaris8_x86_dummy1", "linux_x86_dummy1"]}]. diff --git a/lib/ftp/test/ftp_SUITE_data/vsftpd.conf b/lib/ftp/test/ftp_SUITE_data/vsftpd.conf new file mode 100644 index 0000000000..a5584f5916 --- /dev/null +++ b/lib/ftp/test/ftp_SUITE_data/vsftpd.conf @@ -0,0 +1,26 @@ + +### +### Some parameters are given in the vsftpd start command. +### +### Typical command-line paramters are such that has a file path +### component like cert files. +### + + +listen=YES +listen_port=9999 +run_as_launching_user=YES +ssl_enable=YES +allow_anon_ssl=YES + +background=YES + +write_enable=YES +anonymous_enable=YES +anon_upload_enable=YES +anon_mkdir_write_enable=YES +anon_other_write_enable=YES +anon_world_readable_only=NO + +### Shouldn't be necessary.... +require_ssl_reuse=NO diff --git a/lib/ftp/test/ftp_bench.spec b/lib/ftp/test/ftp_bench.spec new file mode 100644 index 0000000000..4d1ecf8891 --- /dev/null +++ b/lib/ftp/test/ftp_bench.spec @@ -0,0 +1 @@ +{suites,"../ftp_test",[]}. diff --git a/lib/ftp/test/ftp_format_SUITE.erl b/lib/ftp/test/ftp_format_SUITE.erl new file mode 100644 index 0000000000..95d594a44b --- /dev/null +++ b/lib/ftp/test/ftp_format_SUITE.erl @@ -0,0 +1,328 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +-module(ftp_format_SUITE). +-author('ingela@erix.ericsson.se'). + +-include_lib("common_test/include/ct.hrl"). +-include("ftp_internal.hrl"). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{seconds,5}} + ]. + +all() -> + [{group, ftp_response}, format_error]. + +groups() -> + [{ftp_response, [], + [ftp_150, ftp_200, ftp_220, ftp_226, ftp_257, ftp_331, + ftp_425, ftp_other_status_codes, ftp_multiple_lines_status_in_msg, + ftp_multiple_lines, ftp_multipel_ctrl_messages]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + + +init_per_testcase(_, Config) -> + Config. +end_per_testcase(_, _) -> + ok. + +%%------------------------------------------------------------------------- +%% Test cases starts here. +%%------------------------------------------------------------------------- + +ftp_150() -> + [{doc, "Especially check that respons can be devided in a random place."}]. +ftp_150(Config) when is_list(Config) -> + FtpResponse = ["150 ASCII data conn", "ection for /bin/ls ", + "(134.138.177", ".89,50434) (0 bytes).\r\n"], + + "150 ASCII data connection for /bin/ls " + "(134.138.177.89,50434) (0 bytes).\r\n" = Msg = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_prel, _} = ftp_response:interpret(Msg). + +ftp_200() -> + [{doc, "Especially check that respons can be devided after the first status " + "code character and in the end delimiter."}]. +ftp_200(Config) when is_list(Config) -> + FtpResponse = ["2", "00 PORT command successful.", [?CR], [?LF]], + + "200 PORT command successful.\r\n" = Msg = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_compl, _} = ftp_response:interpret(Msg), + ok. + +ftp_220() -> + [{doc, "Especially check that respons can be devided after the " + "first with space "}]. +ftp_220(Config) when is_list(Config) -> + FtpResponse = ["220 ","fingon FTP server (SunOS 5.8) ready.\r\n"], + + "220 fingon FTP server (SunOS 5.8) ready.\r\n" = Msg = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_compl, _} = ftp_response:interpret(Msg), + ok. + +ftp_226() -> + [{doc, "Especially check that respons can be devided after second status code" + " character and in the end delimiter."}]. +ftp_226(Config) when is_list(Config) -> + FtpResponse = ["22" "6 Transfer complete.\r", [?LF]], + + "226 Transfer complete.\r\n" = Msg = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_compl, _} = ftp_response:interpret(Msg), + ok. + +ftp_257() -> + [{doc, "Especially check that quoted chars do not cause a problem."}]. +ftp_257(Config) when is_list(Config) -> + FtpResponse = ["257 \"/\" is current directory.\r\n"], + + "257 \"/\" is current directory.\r\n" = Msg = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_compl, _} = ftp_response:interpret(Msg), + ok. + +ftp_331() -> + [{doc, "Especially check that respons can be devided after the third status " + " status code character."}]. +ftp_331(Config) when is_list(Config) -> + %% Brake before white space after code + FtpResponse = + ["331"," Guest login ok, send ient as password.\r\n"], + + "331 Guest login ok, send ient as password.\r\n" = Msg = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_interm, _} = ftp_response:interpret(Msg), + ok. + +ftp_425() -> + [{doc, "Especially check a message that was received in only one part."}]. +ftp_425(Config) when is_list(Config) -> + FtpResponse = + ["425 Can't build data connection: Connection refused.\r\n"], + + "425 Can't build data connection: Connection refused.\r\n" + = Msg = parse(ftp_response, parse_lines, [[], start], FtpResponse), + {trans_neg_compl, _} = ftp_response:interpret(Msg), + ok. + +ftp_multiple_lines_status_in_msg() -> + [{doc, "check that multiple lines gets parsed correct, even if we have " + " the status code within the msg being sent"}]. +ftp_multiple_lines_status_in_msg(Config) when is_list(Config) -> + ML = "230-User usr-230 is logged in\r\n" ++ + "230 OK. Current directory is /\r\n", + {ok, ML, <<>>} = ftp_response:parse_lines(list_to_binary(ML), [], start), + ok. + +ftp_multiple_lines() -> + [{doc, "Especially check multiple lines devided in significant places"}]. +ftp_multiple_lines(Config) when is_list(Config) -> + FtpResponse = ["21", "4","-The", + " following commands are recognized:\r\n" + " USER EPRT STRU MAIL* ALLO CWD", + " STAT* XRMD \r\n" + " PASS LPRT MODE MSND* " + " REST* XCWD HELP PWD ", [?CRLF], + " ACCT* EPSV RETR MSOM* RNFR LIST " + " NOOP XPWD \r\n", + " REIN* LPSV STOR MSAM* RNTO NLST " + " MKD CDUP \r\n" + " QUIT PASV APPE MRSQ* ABOR SITE* " + " XMKD XCUP \r\n" + " PORT TYPE MLFL* MRCP* DELE SYST " + " RMD STOU \r\n" + "214 (*'s => unimplemented)", [?CR], [?LF]], + + + FtpResponse1 = ["214-", "The", + " following commands are recognized:\r\n" + " USER EPRT STRU MAIL* ALLO CWD", + " STAT* XRMD \r\n" + " PASS LPRT MODE MSND* " + " REST* XCWD HELP PWD ", [?CRLF], + " ACCT* EPSV RETR MSOM* RNFR LIST " + " NOOP XPWD \r\n", + " REIN* LPSV STOR MSAM* RNTO NLST " + " MKD CDUP \r\n" + " QUIT PASV APPE MRSQ* ABOR SITE* " + " XMKD XCUP \r\n" + " PORT TYPE MLFL* MRCP* DELE SYST " + " RMD STOU \r\n" + "2", "14 (*'s => unimplemented)", [?CR], [?LF]], + + FtpResponse2 = ["214-", "The", + " following commands are recognized:\r\n" + " USER EPRT STRU MAIL* ALLO CWD", + " STAT* XRMD \r\n" + " PASS LPRT MODE MSND* " + " REST* XCWD HELP PWD ", [?CRLF], + " ACCT* EPSV RETR MSOM* RNFR LIST " + " NOOP XPWD \r\n", + " REIN* LPSV STOR MSAM* RNTO NLST " + " MKD CDUP \r\n" + " QUIT PASV APPE MRSQ* ABOR SITE* " + " XMKD XCUP \r\n" + " PORT TYPE MLFL* MRCP* DELE SYST " + " RMD STOU \r\n" + "21", "4"," (*'s => unimplemented)", [?CR], [?LF]], + + MultiLineResultStr = + "214-The following commands are recognized:\r\n" + " USER EPRT STRU MAIL* ALLO CWD STAT* " + "XRMD \r\n" + " PASS LPRT MODE MSND* REST* XCWD HELP " + "PWD \r\n" + " ACCT* EPSV RETR MSOM* RNFR LIST NOOP " + "XPWD \r\n" + " REIN* LPSV STOR MSAM* RNTO NLST MKD " + "CDUP \r\n" + " QUIT PASV APPE MRSQ* ABOR SITE* XMKD " + "XCUP \r\n" + " PORT TYPE MLFL* MRCP* DELE SYST RMD " + "STOU \r\n" + "214 (*'s => unimplemented)\r\n", + + MultiLineResultStr = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_compl, _} = ftp_response:interpret(MultiLineResultStr), + + MultiLineResultStr = parse(ftp_response, parse_lines, [[], start], + FtpResponse1), + + MultiLineResultStr = parse(ftp_response, parse_lines, [[], start], + FtpResponse2), + ok. + +ftp_other_status_codes() -> + [{doc, "Check that other valid status codes, than the ones above, are handled" + "by ftp_response:interpret/1. Note there are som ftp status codes" + "that will not be received with the current ftp instruction support," + "they are not included here."}]. +ftp_other_status_codes(Config) when is_list(Config) -> + + %% 1XX + {pos_prel, _ } = ftp_response:interpret("120 Foobar\r\n"), + + %% 2XX + {pos_compl, _ } = ftp_response:interpret("202 Foobar\r\n"), + {pos_compl, _ } = ftp_response:interpret("221 Foobar\r\n"), + {pos_compl, _ } = ftp_response:interpret("227 Foobar\r\n"), + {pos_compl, _ } = ftp_response:interpret("230 Foobar\r\n"), + {pos_compl, _ } = ftp_response:interpret("250 Foobar\r\n"), + + %% 3XX + {pos_interm_acct, _ } = ftp_response:interpret("332 Foobar\r\n"), + {pos_interm, _ } = ftp_response:interpret("350 Foobar\r\n"), + + %% 4XX + {trans_neg_compl, _ } = ftp_response:interpret("421 Foobar\r\n"), + {trans_neg_compl, _ } = ftp_response:interpret("426 Foobar\r\n"), + {enofile, _ } = ftp_response:interpret("450 Foobar\r\n"), + {trans_neg_compl, _ } = ftp_response:interpret("451 Foobar\r\n"), + {etnospc, _ } = ftp_response:interpret("452 Foobar\r\n"), + + %% 5XX + {perm_neg_compl, _ } = ftp_response:interpret("500 Foobar\r\n"), + {perm_neg_compl, _ } = ftp_response:interpret("501 Foobar\r\n"), + {perm_neg_compl, _ } = ftp_response:interpret("503 Foobar\r\n"), + {perm_neg_compl, _ } = ftp_response:interpret("504 Foobar\r\n"), + {elogin, _ } = ftp_response:interpret("530 Foobar\r\n"), + {perm_neg_compl, _ } = ftp_response:interpret("532 Foobar\r\n"), + {epath, _ } = ftp_response:interpret("550 Foobar\r\n"), + {epnospc, _ } = ftp_response:interpret("552 Foobar\r\n"), + {efnamena, _ } = ftp_response:interpret("553 Foobar\r\n"), + ok. + +ftp_multipel_ctrl_messages() -> + [{doc, "The ftp server may send more than one control message as a reply," + "check that they are handled one at the time."}]. +ftp_multipel_ctrl_messages(Config) when is_list(Config) -> + FtpResponse = ["200 PORT command successful.\r\n200 Foobar\r\n"], + + {"200 PORT command successful.\r\n" = Msg, NextMsg} = + parse(ftp_response, parse_lines, [[], start], FtpResponse), + {pos_compl, _} = ftp_response:interpret(Msg), + NewMsg = parse(ftp_response, parse_lines, [[], start], NextMsg), + {pos_compl, _} = ftp_response:interpret(NewMsg), + ok. + + +%%------------------------------------------------------------------------- +format_error(Config) when is_list(Config) -> + "Synchronisation error during chunk sending." = + ftp:formaterror(echunk), + "Session has been closed." = ftp:formaterror(eclosed), + "Connection to remote server prematurely closed." = + ftp:formaterror(econn), + "File or directory already exists." = ftp:formaterror(eexists), + "Host not found, FTP server not found, or connection rejected." = + ftp:formaterror(ehost), + "User not logged in." = ftp:formaterror(elogin), + "Term is not a binary." = ftp:formaterror(enotbinary), + "No such file or directory, already exists, or permission denied." + = ftp:formaterror(epath), + "No such type." = ftp:formaterror(etype), + "User name or password not valid." = ftp:formaterror(euser), + "Insufficient storage space in system." = ftp:formaterror(etnospc), + "Exceeded storage allocation (for current directory or dataset)." + = ftp:formaterror(epnospc), + "File name not allowed." = ftp:formaterror(efnamena), + "Unknown error: foobar" = ftp:formaterror({error, foobar}). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +parse(Module, Function, Args, Bin) when is_binary(Bin) -> + parse(Module, Function, Args, [binary_to_list(Bin)]); + +parse(Module, Function, [AccLines, StatusCode], [Data | Rest]) -> + case Module:Function(list_to_binary(Data), AccLines, StatusCode) of + {ok, Result, <<>>} -> + Result; + {ok, Result, Next} -> + {Result, Next}; + {continue, {NewData, NewAccLines, NewStatusCode}} -> + case Rest of + [] -> + ct:fail({wrong_input, Data, Rest}); + [_ | _] -> + parse(Module, Function, [NewAccLines, NewStatusCode], + [binary_to_list(NewData) ++ hd(Rest) | tl(Rest)]) + end + end. diff --git a/lib/ftp/test/ftp_internal.hrl b/lib/ftp/test/ftp_internal.hrl new file mode 120000 index 0000000000..2ae5c46460 --- /dev/null +++ b/lib/ftp/test/ftp_internal.hrl @@ -0,0 +1 @@ +../src/ftp_internal.hrl \ No newline at end of file diff --git a/lib/ftp/test/ftp_property_test_SUITE.erl b/lib/ftp/test/ftp_property_test_SUITE.erl new file mode 100644 index 0000000000..b314882296 --- /dev/null +++ b/lib/ftp/test/ftp_property_test_SUITE.erl @@ -0,0 +1,53 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +%%% Run like this: +%%% ct:run_test([{suite,"ftp_property_test_SUITE"}, {logdir,"/ldisk/OTP/LOG"}]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% %%% +%%% WARNING %%% +%%% %%% +%%% This is experimental code which may be changed or removed %%% +%%% anytime without any warning. %%% +%%% %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(ftp_property_test_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +all() -> [prop_ftp_case]. + + +init_per_suite(Config) -> + inets:start(), + ct_property_test:init_per_suite(Config). + + +%%%---- test case +prop_ftp_case(Config) -> + ct_property_test:quickcheck( + ftp_simple_client_server:prop_ftp(Config), + Config + ). diff --git a/lib/ftp/test/inets_test_lib.erl b/lib/ftp/test/inets_test_lib.erl new file mode 100644 index 0000000000..2529cc5f9b --- /dev/null +++ b/lib/ftp/test/inets_test_lib.erl @@ -0,0 +1,596 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +-module(inets_test_lib). + +-include("inets_test_lib.hrl"). +-include_lib("inets/src/http_lib/http_internal.hrl"). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +%% -- Misc os command and stuff + +has_ipv6_support() -> + tsp("has_ipv6_support -> no ipv6_hosts config"), + {ok, Hostname} = inet:gethostname(), + case inet:getaddrs(Hostname, inet6) of + {ok, [Addr|_]} when is_tuple(Addr) andalso + (element(1, Addr) =/= 0) -> + %% We actually need to test that the addr can be used, + %% this is done by attempting to create a (tcp) + %% listen socket + tsp("has_ipv6_support -> check Addr: ~p", [Addr]), + case (catch gen_tcp:listen(0, [inet6, {ip, Addr}])) of + {ok, LSock} -> + tsp("has_ipv6_support -> we are ipv6 host"), + gen_tcp:close(LSock), + {ok, Addr}; + _ -> + undefined + end; + _ -> + undefined + end. + +has_ipv6_support(Config) -> + case lists:keysearch(ipv6_hosts, 1, Config) of + false -> + %% Do a basic check to se if + %% our own host has a working IPv6 address... + has_ipv6_support(); + + {value, {_, Hosts}} when is_list(Hosts) -> + %% Check if our host is in the list of *known* IPv6 hosts + tsp("has_ipv6_support -> Hosts: ~p", [Hosts]), + {ok, Hostname} = inet:gethostname(), + case lists:member(list_to_atom(Hostname), Hosts) of + true -> + tsp("has_ipv6_support -> we are known ipv6 host"), + {ok, [Addr|_]} = inet:getaddrs(Hostname, inet6), + {ok, Addr}; + false -> + undefined + end; + + _ -> + undefined + + end. + +oscmd(Cmd) -> + string:strip(os:cmd(Cmd), right, $\n). + + +print_system_info([]) -> + do_print_system_info("System Info"); +print_system_info(Prefix) when is_list(Prefix) -> + NewPrefix = lists:flatten(io_lib:format("~s: System Info", [Prefix])), + do_print_system_info(NewPrefix). + +do_print_system_info(Prefix) -> + tsp("~s => " + "~n" + "~n OS Type: ~p" + "~n OS version: ~p" + "~n Sys Arch: ~p" + "~n CPU Topology: ~p" + "~n Num logical procs: ~p" + "~n SMP support: ~p" + "~n Num schedulers: ~p" + "~n Scheduler bindings: ~p" + "~n Wordsize: ~p" + "~n~n", [Prefix, + os:type(), os:version(), + erlang:system_info(system_architecture), + erlang:system_info(cpu_topology), + erlang:system_info(logical_processors), + erlang:system_info(smp_support), + erlang:system_info(schedulers), + erlang:system_info(scheduler_bindings), + erlang:system_info(wordsize)]), + ok. + + +run_on_windows(Fun) -> + run_on_os(windows, Fun). + +run_on_os(windows, Fun) -> + case os:type() of + {win32, _} -> + Fun(); + _ -> + ok + end. + + +%% -- Misc node operation wrapper functions -- + +start_node(Name) -> + Pa = filename:dirname(code:which(?MODULE)), + Args = case init:get_argument('CC_TEST') of + {ok, [[]]} -> + " -pa /clearcase/otp/libraries/snmp/ebin "; + {ok, [[Path]]} -> + " -pa " ++ Path; + error -> + "" + end, + A = Args ++ " -pa " ++ Pa, + Opts = [{cleanup,false}, {args, A}], + case (catch test_server:start_node(Name, slave, Opts)) of + {ok, Node} -> + Node; + Else -> + exit({failed_starting_node, Name, Else}) + end. + +stop_node(Node) -> + rpc:cast(Node, erlang, halt, []), + await_stopped(Node, 5). + +await_stopped(_, 0) -> + ok; +await_stopped(Node, N) -> + Nodes = erlang:nodes(), + case lists:member(Node, Nodes) of + true -> + sleep(1000), + await_stopped(Node, N-1); + false -> + ok + end. + + +%% ---------------------------------------------------------------- +%% Ensure apps are started +%% This to ensure we dont attempt to run teatcases on platforms +%% where there is no working ssl app. + +ensure_started([]) -> + ok; +ensure_started([App|Apps]) -> + ensure_started(App), + ensure_started(Apps); +ensure_started(crypto = App) -> + %% We have to treat crypto in this special way because + %% only this function ensures that the NIF lib is actually + %% loaded. And only by loading that lib can we know if it + %% is even possible to run crypto. + do_ensure_started(App, fun() -> crypto:start() end); +ensure_started(App) when is_atom(App) -> + do_ensure_started(App, fun() -> application:start(App) end). + +do_ensure_started(App, Start) when is_function(Start) -> + case (catch Start()) of + ok -> + ok; + {error, {already_started, _}} -> + ok; + Error -> + throw({error, {failed_starting, App, Error}}) + end. + + +ensure_loaded(App) -> + case application:load(App) of + ok -> + ok; + {error, {already_loaded,inets}} -> + ok; + Error -> + Error + end. + + + +%% ---------------------------------------------------------------- +%% HTTPD starter functions +%% + +start_http_server(Conf) -> + start_http_server(Conf, ?HTTP_DEFAULT_SSL_KIND). + +start_http_server(Conf, essl = _SslTag) -> + tsp("start_http_server(essl) -> try start crypto"), + application:start(crypto), + tsp("start_http_server(essl) -> try start public_key"), + application:start(public_key), + do_start_http_server(Conf); +start_http_server(Conf, SslTag) -> + tsp("start_http_server(~w) -> entry", [SslTag]), + do_start_http_server(Conf). + +do_start_http_server(Conf) -> + tsp("do_start_http_server -> entry with" + "~n Conf: ~p" + "~n", [Conf]), + tsp("do_start_http_server -> load inets"), + case ensure_loaded(inets) of + ok -> + tsp("do_start_http_server -> inets loaded - now set_env for httpd"), + case application:set_env(inets, services, [{httpd, Conf}]) of + ok -> + tsp("do_start_http_server -> " + "httpd conf stored in inets app env"), + case (catch application:start(inets)) of + ok -> + tsp("do_start_http_server -> inets started"), + ok; + Error1 -> + tsp(" Failed starting application: " + "~n Error1: ~p", [Error1]), + tsf({failed_starting_inets, Error1}) + end; + Error2 -> + tsp(" Failed set application env: " + "~n Error: ~p", [Error2]), + tsf({failed_set_env, Error2}) + end; + {error, Reason} -> + tsp("do_start_http_server -> failed loading inets" + "~n Reason: ~p", [Reason]), + tsf({failed_loading_inets, Reason}) + end. + +start_http_server_ssl(FileName) -> + start_http_server_ssl(FileName, ?HTTP_DEFAULT_SSL_KIND). + +start_http_server_ssl(FileName, essl = _SslTag) -> + application:start(crypto), + do_start_http_server_ssl(FileName); +start_http_server_ssl(FileName, _SslTag) -> + do_start_http_server_ssl(FileName). + +do_start_http_server_ssl(FileName) -> + tsp("start (ssl) http server with " + "~n FileName: ~p" + "~n", [FileName]), + application:start(ssl), + catch do_start_http_server(FileName). + + + +%% ---------------------------------------------------------------------- +%% print functions +%% + +info(F, A, Mod, Line) -> + print("INF ", F, A, Mod, Line). + +log(F, A, Mod, Line) -> + print("LOG ", F, A, Mod, Line). + +debug(F, A, Mod, Line) -> + print("DBG ", F, A, Mod, Line). + +print(P, F, A, Mod, Line) -> + io:format("~s[~p:~p:~p] : " ++ F ++ "~n", [P, self(), Mod, Line| A]). + +print(F, A, Mod, Line) -> + print("", F, A, Mod, Line). + +hostname() -> + {ok, Name} = inet:gethostname(), + Name. + +from(H, [H | T]) -> T; +from(H, [_ | T]) -> from(H, T); +from(_, []) -> []. + + +copy_file(File, From, To) -> + file:copy(filename:join(From, File), filename:join(To, File)). + +copy_files(FromDir, ToDir) -> + {ok, Files} = file:list_dir(FromDir), + lists:foreach(fun(File) -> + FullPath = filename:join(FromDir, File), + case filelib:is_file(FullPath) of + true -> + file:copy(FullPath, + filename:join(ToDir, File)); + false -> + ok + end + end, Files). + + +copy_dirs(FromDirRoot, ToDirRoot) -> + case file:list_dir(FromDirRoot) of + {ok, Files} -> + lists:foreach( + fun(FileOrDir) -> + %% Check if it's a directory or a file + case filelib:is_dir(filename:join(FromDirRoot, + FileOrDir)) of + true -> + FromDir = filename:join([FromDirRoot, FileOrDir]), + ToDir = filename:join([ToDirRoot, FileOrDir]), + case file:make_dir(ToDir) of + ok -> + copy_dirs(FromDir, ToDir); + {error, Reason} -> + tsp(" Failed creating directory: " + "~n ToDir: ~p" + "~n Reason: ~p" + "~nwhen" + "~n ToDirRoot: ~p" + "~n ToDirRoot file info: ~p", + [ToDir, + Reason, + ToDirRoot, + file:read_file_info(ToDirRoot)]), + tsf({failed_copy_dir, ToDir, Reason}) + end; + false -> + copy_file(FileOrDir, FromDirRoot, ToDirRoot) + end + end, Files); + {error, Reason} -> + tsp(" Failed get directory file list: " + "~n FromDirRoot: ~p" + "~n Reason: ~p" + "~nwhen" + "~n FromDirRoot file info: ~p", + [FromDirRoot, + Reason, + file:read_file_info(FromDirRoot)]), + tsf({failed_list_dir, FromDirRoot, Reason}) + end. + + + +del_dirs(Dir) -> + case file:list_dir(Dir) of + {ok, []} -> + file:del_dir(Dir); + {ok, Files} -> + lists:foreach(fun(File) -> + FullPath = filename:join(Dir,File), + case filelib:is_dir(FullPath) of + true -> + del_dirs(FullPath), + file:del_dir(FullPath); + false -> + file:delete(FullPath) + end + end, Files); + _ -> + ok + end. + +check_body(Body) -> + case string:rstr(Body, "") of + 0 -> + case string:rstr(Body, "") of + 0 -> + tsp("Body ~p", [Body]), + tsf(did_not_receive_whole_body); + _ -> + ok + end; + _ -> + ok + end. + +%% ---------------------------------------------------------------- +%% Conditional skip of testcases +%% + +non_pc_tc_maybe_skip(Config, Condition, File, Line) + when is_list(Config) andalso is_function(Condition) -> + %% Check if we shall skip the skip + case os:getenv("TS_OS_BASED_SKIP") of + "false" -> + ok; + _ -> + case lists:keysearch(ts, 1, Config) of + {value, {ts, inets}} -> + %% Always run the testcase if we are using our own + %% test-server... + ok; + _ -> + case (catch Condition()) of + true -> + skip(non_pc_testcase, File, Line); + _ -> + ok + end + end + end. + + +os_based_skip(any) -> + true; +os_based_skip(Skippable) when is_list(Skippable) -> + {OsFam, OsName} = + case os:type() of + {_Fam, _Name} = FamAndName -> + FamAndName; + Fam -> + {Fam, undefined} + end, + case lists:member(OsFam, Skippable) of + true -> + true; + false -> + case lists:keysearch(OsFam, 1, Skippable) of + {value, {OsFam, OsName}} -> + true; + {value, {OsFam, OsNames}} when is_list(OsNames) -> + lists:member(OsName, OsNames); + _ -> + false + end + end; +os_based_skip(_) -> + false. + + +%% ---------------------------------------------------------------------- +%% Socket functions: +%% open(SocketType, Host, Port) -> {ok, Socket} | {error, Reason} +%% SocketType -> ssl | ip_comm +%% Host -> atom() | string() | {A, B, C, D} +%% Port -> integer() + +connect_bin(SockType, Host, Port) -> + connect_bin(SockType, Host, Port, []). + +connect_bin(ssl, Host, Port, Opts0) -> + Opts = [binary, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(essl, Host, Port, Opts0) -> + Opts = [{ssl_imp, new}, binary, {packet,0}| Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(ip_comm, Host, Port, Opts0) -> + Opts = [binary, {packet, 0} | Opts0], + connect(ip_comm, Host, Port, Opts); +connect_bin(Type, Host, Port, Opts) -> + connect(Type, Host, Port, Opts). + +connect_byte(SockType, Host, Port) -> + connect_byte(SockType, Host, Port, []). + +connect_byte(ssl, Host, Port, Opts0) -> + Opts = [{packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(essl, Host, Port, Opts0) -> + Opts = [{ssl_imp, new}, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(ip_comm, Host, Port, Opts0) -> + Opts = [{packet,0} | Opts0], + connect(ip_comm, Host, Port, Opts); +connect_byte(Type, Host, Port, Opts) -> + connect(Type, Host, Port, Opts). + +connect(ip_comm, Host, Port, Opts) -> + gen_tcp:connect(Host, Port, Opts); +connect(ssl, Host, Port, Opts) -> + ssl:connect(Host, Port, Opts); +connect(openssl_port, Host, Port, Opts) -> + CaCertFile = proplists:get_value(cacertfile, Opts), + Cmd = "openssl s_client -quiet -port " ++ integer_to_list(Port) ++ " -host " ++ Host + ++ " -CAfile " ++ CaCertFile, + ct:log("openssl cmd: ~p~n", [Cmd]), + OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + read_junk(OpensslPort), + {ok, OpensslPort}. + +send(ssl, Socket, Data) -> + ssl:send(Socket, Data); +send(essl, Socket, Data) -> + ssl:send(Socket, Data); +send(ip_comm,Socket,Data) -> + gen_tcp:send(Socket,Data); +send(openssl_port, Port, Data) -> + true = port_command(Port, Data), + ok. +close(ssl,Socket) -> + catch ssl:close(Socket); +close(essl,Socket) -> + catch ssl:close(Socket); +close(ip_comm,Socket) -> + catch gen_tcp:close(Socket); +close(openssl_port, Port) -> + exit(Port, normal). + + +hours(N) -> trunc(N * 1000 * 60 * 60). +minutes(N) -> trunc(N * 1000 * 60). +seconds(N) -> trunc(N * 1000). + + +sleep(infinity) -> + receive + after infinity -> + ok + end; +sleep(MSecs) -> + receive + after trunc(MSecs) -> + ok + end, + ok. + + +skip(Reason, File, Line) -> + exit({skipped, {Reason, File, Line}}). + +fail(Reason, File, Line) -> + String = lists:flatten(io_lib:format("Failure ~p(~p): ~p~n", + [File, Line, Reason])), + tsf(String). + + + +flush() -> + receive + Msg -> + [Msg | flush()] + after 1000 -> + [] + end. + + +tsp(F) -> + tsp(F, []). +tsp(F, A) -> + Timestamp = inets_lib:formated_timestamp(), + ct:pal("*** ~s ~p ~p " ++ F ++ "~n", + [Timestamp, node(), self() | A]). + +tsf(Reason) -> + ct:fail(Reason). + +tss(Time) -> + ct:sleep(Time). + +timestamp() -> + http_util:timestamp(). + +start_apps(Apps) -> + lists:foreach(fun(App) -> + application:stop(App), + application:start(App) + end, Apps). +stop_apps(Apps) -> + lists:foreach(fun(App) -> + application:stop(App) + end, Apps). + +inet_port(Node) -> + {Port, Socket} = do_inet_port(Node), + rpc:call(Node, gen_tcp, close, [Socket]), + Port. + +do_inet_port(Node) -> + {ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]), + {ok, Port} = rpc:call(Node, inet, port, [Socket]), + {Port, Socket}. + +read_junk(OpensslPort) -> + receive + {OpensslPort, _} -> + read_junk(OpensslPort) + after 500 -> + ok + end. diff --git a/lib/ftp/test/inets_test_lib.hrl b/lib/ftp/test/inets_test_lib.hrl new file mode 100644 index 0000000000..d436395290 --- /dev/null +++ b/lib/ftp/test/inets_test_lib.hrl @@ -0,0 +1,28 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% +%%---------------------------------------------------------------------- +%% Purpose: Define common macros for testing +%%---------------------------------------------------------------------- +%% - Misc macros - + +-define(ENSURE_STARTED(A), inets_test_lib:ensure_started(A)). + + diff --git a/lib/ftp/test/property_test/README b/lib/ftp/test/property_test/README new file mode 100644 index 0000000000..57602bf719 --- /dev/null +++ b/lib/ftp/test/property_test/README @@ -0,0 +1,12 @@ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% %%% +%%% WARNING %%% +%%% %%% +%%% This is experimental code which may be changed or removed %%% +%%% anytime without any warning. %%% +%%% %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +The test in this directory are written assuming that the user has a QuickCheck license. They are to be run manually. Some may be possible to be run with other tools, e.g. PropEr. + diff --git a/lib/ftp/test/property_test/ftp_simple_client_server.erl b/lib/ftp/test/property_test/ftp_simple_client_server.erl new file mode 100644 index 0000000000..c98d87b514 --- /dev/null +++ b/lib/ftp/test/property_test/ftp_simple_client_server.erl @@ -0,0 +1,307 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(ftp_simple_client_server). + +-compile(export_all). + +-ifndef(EQC). +-ifndef(PROPER). +-define(EQC,true). +%%-define(PROPER,true). +-endif. +-endif. + + +-ifdef(EQC). + +-include_lib("eqc/include/eqc.hrl"). +-include_lib("eqc/include/eqc_statem.hrl"). +-define(MOD_eqc, eqc). +-define(MOD_eqc_gen, eqc_gen). +-define(MOD_eqc_statem, eqc_statem). + +-else. +-ifdef(PROPER). + +-include_lib("proper/include/proper.hrl"). +-define(MOD_eqc, proper). +-define(MOD_eqc_gen, proper_gen). +-define(MOD_eqc_statem, proper_statem). + +-endif. +-endif. + +-record(state, { + initialized = false, + priv_dir, + data_dir, + servers = [], % [ {IP,Port,Userid,Pwd} ] + clients = [], % [ client_ref() ] + store = [] % [ {Name,Contents} ] + }). + +-define(fmt(F,A), io:format(F,A)). +%%-define(fmt(F,A), ok). + +-define(v(K,L), proplists:get_value(K,L)). + +%%%================================================================ +%%% +%%% Properties +%%% + +%% This function is for normal eqc calls: +prop_ftp() -> + {ok,PWD} = file:get_cwd(), + prop_ftp(filename:join([PWD,?MODULE_STRING++"_data"]), + filename:join([PWD,?MODULE_STRING,"_files"])). + +%% This function is for calls from common_test test cases: +prop_ftp(Config) -> + prop_ftp(filename:join([?v(property_dir,Config), ?MODULE_STRING++"_data"]), + ?v(priv_dir,Config) ). + + +prop_ftp(DataDir, PrivDir) -> + S0 = #state{data_dir = DataDir, + priv_dir = PrivDir}, + ?FORALL(Cmds, more_commands(10,commands(?MODULE,S0)), + aggregate(command_names(Cmds), + begin {_H,S,Result} = run_commands(?MODULE,Cmds), + % io:format('**** Result=~p~n',[Result]), + % io:format('**** S=~p~n',[S]), + % io:format('**** _H=~p~n',[_H]), + % io:format('**** Cmds=~p~n',[Cmds]), + [cmnd_stop_server(X) || X <- S#state.servers], + [inets:stop(ftpc,X) || {ok,X} <- S#state.clients], + Result==ok + end) + ). + +%%%================================================================ +%%% +%%% State model +%%% + +%% @doc Returns the state in which each test case starts. (Unless a different +%% initial state is supplied explicitly to, e.g. commands/2.) +-spec initial_state() ->?MOD_eqc_statem:symbolic_state(). +initial_state() -> + ?fmt("Initial_state()~n",[]), + #state{}. + +%% @doc Command generator, S is the current state +-spec command(S :: ?MOD_eqc_statem:symbolic_state()) -> ?MOD_eqc_gen:gen(eqc_statem:call()). + +command(#state{initialized=false, + priv_dir=PrivDir}) -> + {call,?MODULE,cmnd_init,[PrivDir]}; + +command(#state{servers=[], + priv_dir=PrivDir, + data_dir=DataDir}) -> + {call,?MODULE,cmnd_start_server,[PrivDir,DataDir]}; + +command(#state{servers=Ss=[_|_], + clients=[]}) -> + {call,?MODULE,cmnd_start_client,[oneof(Ss)]}; + +command(#state{servers=Ss=[_|_], + clients=Cs=[_|_], + store=Store=[_|_] + }) -> + frequency([ + { 5, {call,?MODULE,cmnd_start_client,[oneof(Ss)]}}, + { 5, {call,?MODULE,cmnd_stop_client,[oneof(Cs)]}}, + {10, {call,?MODULE,cmnd_put,[oneof(Cs),file_path(),file_contents()]}}, + {20, {call,?MODULE,cmnd_get,[oneof(Cs),oneof(Store)]}}, + {10, {call,?MODULE,cmnd_delete,[oneof(Cs),oneof(Store)]}} + ]); + +command(#state{servers=Ss=[_|_], + clients=Cs=[_|_], + store=[] + }) -> + frequency([ + {5, {call,?MODULE,cmnd_start_client,[oneof(Ss)]}}, + {5, {call,?MODULE,cmnd_stop_client,[oneof(Cs)]}}, + {10, {call,?MODULE,cmnd_put,[oneof(Cs),file_path(),file_contents()]}} + ]). + +%% @doc Precondition, checked before command is added to the command sequence. +-spec precondition(S :: ?MOD_eqc_statem:symbolic_state(), C :: ?MOD_eqc_statem:call()) -> boolean(). + +precondition(#state{clients=Cs}, {call, _, cmnd_put, [C,_,_]}) -> lists:member(C,Cs); + +precondition(#state{clients=Cs, store=Store}, + {call, _, cmnd_get, [C,X]}) -> lists:member(C,Cs) andalso lists:member(X,Store); + +precondition(#state{clients=Cs, store=Store}, + {call, _, cmnd_delete, [C,X]}) -> lists:member(C,Cs) andalso lists:member(X,Store); + +precondition(#state{servers=Ss}, {call, _, cmnd_start_client, _}) -> Ss =/= []; + +precondition(#state{clients=Cs}, {call, _, cmnd_stop_client, [C]}) -> lists:member(C,Cs); + +precondition(#state{initialized=IsInit}, {call, _, cmnd_init, _}) -> IsInit==false; + +precondition(_S, {call, _, _, _}) -> true. + + +%% @doc Postcondition, checked after command has been evaluated +%% Note: S is the state before next_state(S,_,C) +-spec postcondition(S :: ?MOD_eqc_statem:dynamic_state(), C :: ?MOD_eqc_statem:call(), + Res :: term()) -> boolean(). + +postcondition(_S, {call, _, cmnd_get, [_,{_Name,Expected}]}, {ok,Value}) -> + Value == Expected; + +postcondition(S, {call, _, cmnd_delete, [_,{Name,_Expected}]}, ok) -> + ?fmt("file:read_file(..) = ~p~n",[file:read_file(filename:join(S#state.priv_dir,Name))]), + {error,enoent} == file:read_file(filename:join(S#state.priv_dir,Name)); + +postcondition(S, {call, _, cmnd_put, [_,Name,Value]}, ok) -> + {ok,Bin} = file:read_file(filename:join(S#state.priv_dir,Name)), + Bin == unicode:characters_to_binary(Value); + +postcondition(_S, {call, _, cmnd_stop_client, _}, ok) -> true; + +postcondition(_S, {call, _, cmnd_start_client, _}, {ok,_}) -> true; + +postcondition(_S, {call, _, cmnd_init, _}, ok) -> true; + +postcondition(_S, {call, _, cmnd_start_server, _}, {ok,_}) -> true. + + +%% @doc Next state transformation, S is the current state. Returns next state. +-spec next_state(S :: ?MOD_eqc_statem:symbolic_state(), + V :: ?MOD_eqc_statem:var(), + C :: ?MOD_eqc_statem:call()) -> ?MOD_eqc_statem:symbolic_state(). + +next_state(S, _V, {call, _, cmnd_put, [_,Name,Val]}) -> + S#state{store = [{Name,Val} | lists:keydelete(Name,1,S#state.store)]}; + +next_state(S, _V, {call, _, cmnd_delete, [_,{Name,_Val}]}) -> + S#state{store = lists:keydelete(Name,1,S#state.store)}; + +next_state(S, V, {call, _, cmnd_start_client, _}) -> + S#state{clients = [V | S#state.clients]}; + +next_state(S, V, {call, _, cmnd_start_server, _}) -> + S#state{servers = [V | S#state.servers]}; + +next_state(S, _V, {call, _, cmnd_stop_client, [C]}) -> + S#state{clients = S#state.clients -- [C]}; + +next_state(S, _V, {call, _, cmnd_init, _}) -> + S#state{initialized=true}; + +next_state(S, _V, {call, _, _, _}) -> + S. + +%%%================================================================ +%%% +%%% Data model +%%% + +file_path() -> non_empty(list(alphanum_char())). +%%file_path() -> non_empty( list(oneof([alphanum_char(), utf8_char()])) ). + +%%file_contents() -> list(alphanum_char()). +file_contents() -> list(oneof([alphanum_char(), utf8_char()])). + +alphanum_char() -> oneof(lists:seq($a,$z) ++ lists:seq($A,$Z) ++ lists:seq($0,$9)). + +utf8_char() -> oneof("åäöÅÄÖ話话カタカナひらがな"). + +%%%================================================================ +%%% +%%% Commands doing something with the System Under Test +%%% + +cmnd_init(PrivDir) -> + ?fmt('Call cmnd_init(~p)~n',[PrivDir]), + os:cmd("killall vsftpd"), + clear_files(PrivDir), + ok. + +cmnd_start_server(PrivDir, DataDir) -> + ?fmt('Call cmnd_start_server(~p, ~p)~n',[PrivDir,DataDir]), + Cmnd = ["vsftpd ", filename:join(DataDir,"vsftpd.conf"), + " -oftpd_banner=erlang_otp_testing" + " -oanon_root=",PrivDir + ], + ?fmt("Cmnd=~s~n",[Cmnd]), + case os:cmd(Cmnd) of + [] -> + {ok,{"localhost",9999,"ftp","usr@example.com"}}; + Other -> + {error,Other} + end. + +cmnd_stop_server({ok,{_Host,Port,_Usr,_Pwd}}) -> + os:cmd("kill `netstat -tpln | grep "++integer_to_list(Port)++" | awk '{print $7}' | awk -F/ '{print $1}'`"). + +cmnd_start_client({ok,{Host,Port,Usr,Pwd}}) -> + ?fmt('Call cmnd_start_client(~p)...',[{Host,Port,Usr,Pwd}]), + case inets:start(ftpc, [{host,Host},{port,Port}]) of + {ok,Client} -> + ?fmt("~p...",[{ok,Client}]), + case ftp:user(Client, Usr, Pwd) of + ok -> + ?fmt("OK!~n",[]), + {ok,Client}; + Other -> + ?fmt("Other1=~p~n",[Other]), + inets:stop(ftpc,Client), Other + end; + Other -> + ?fmt("Other2=~p~n",[Other]), + Other + end. + +cmnd_stop_client({ok,Client}) -> + ?fmt('Call cmnd_stop_client(~p)~n',[Client]), + inets:stop(ftpc, Client). %% -> ok | Other + +cmnd_delete({ok,Client}, {Name,_ExpectedValue}) -> + ?fmt('Call cmnd_delete(~p, ~p)~n',[Client,Name]), + R=ftp:delete(Client, Name), + ?fmt("R=~p~n",[R]), + R. + +cmnd_put({ok,Client}, Name, Value) -> + ?fmt('Call cmnd_put(~p, ~p, ~p)...',[Client, Name, Value]), + R = ftp:send_bin(Client, unicode:characters_to_binary(Value), Name), % ok | {error,Error} + ?fmt('~p~n',[R]), + R. + +cmnd_get({ok,Client}, {Name,_ExpectedValue}) -> + ?fmt('Call cmnd_get(~p, ~p)~n',[Client,Name]), + case ftp:recv_bin(Client, Name) of + {ok,Bin} -> {ok, unicode:characters_to_list(Bin)}; + Other -> Other + end. + + +clear_files(Dir) -> + os:cmd(["rm -fr ",filename:join(Dir,"*")]). diff --git a/lib/ftp/test/property_test/ftp_simple_client_server_data/vsftpd.conf b/lib/ftp/test/property_test/ftp_simple_client_server_data/vsftpd.conf new file mode 100644 index 0000000000..fd48e2abf0 --- /dev/null +++ b/lib/ftp/test/property_test/ftp_simple_client_server_data/vsftpd.conf @@ -0,0 +1,26 @@ + +### +### Some parameters are given in the vsftpd start command. +### +### Typical command-line paramters are such that has a file path +### component like cert files. +### + + +listen=YES +listen_port=9999 +run_as_launching_user=YES +ssl_enable=NO +#allow_anon_ssl=YES + +background=YES + +write_enable=YES +anonymous_enable=YES +anon_upload_enable=YES +anon_mkdir_write_enable=YES +anon_other_write_enable=YES +anon_world_readable_only=NO + +### Shouldn't be necessary.... +require_ssl_reuse=NO diff --git a/lib/ftp/vsn.mk b/lib/ftp/vsn.mk new file mode 100644 index 0000000000..901ff6d450 --- /dev/null +++ b/lib/ftp/vsn.mk @@ -0,0 +1,24 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2001-2018. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# %CopyrightEnd% + +APPLICATION = ftp +FTP_VSN = 1.0.0 +PRE_VSN = +APP_VSN = "$(APPLICATION)-$(FTP_VSN)$(PRE_VSN)" diff --git a/lib/inets/doc/archive/rfc2428.txt b/lib/inets/doc/archive/rfc2428.txt deleted file mode 100644 index a6ec3535ed..0000000000 --- a/lib/inets/doc/archive/rfc2428.txt +++ /dev/null @@ -1,451 +0,0 @@ - - - - - - -Network Working Group M. Allman -Request for Comments: 2428 NASA Lewis/Sterling Software -Category: Standards Track S. Ostermann - Ohio University - C. Metz - The Inner Net - September 1998 - - - FTP Extensions for IPv6 and NATs - -Status of this Memo - - This document specifies an Internet standards track protocol for the - Internet community, and requests discussion and suggestions for - improvements. Please refer to the current edition of the "Internet - Official Protocol Standards" (STD 1) for the standardization state - and status of this protocol. Distribution of this memo is unlimited. - -Copyright Notice - - Copyright (C) The Internet Society (1998). All Rights Reserved. - -Abstract - - The specification for the File Transfer Protocol assumes that the - underlying network protocol uses a 32-bit network address - (specifically IP version 4). With the deployment of version 6 of the - Internet Protocol, network addresses will no longer be 32-bits. This - paper specifies extensions to FTP that will allow the protocol to - work over IPv4 and IPv6. In addition, the framework defined can - support additional network protocols in the future. - -1. Introduction - - The keywords, such as MUST and SHOULD, found in this document are - used as defined in RFC 2119 [Bra97]. - - The File Transfer Protocol [PR85] only provides the ability to - communicate information about IPv4 data connections. FTP assumes - network addresses will be 32 bits in length. However, with the - deployment of version 6 of the Internet Protocol [DH96] addresses - will no longer be 32 bits long. RFC 1639 [Pis94] specifies - extensions to FTP to enable its use over various network protocols. - Unfortunately, the mechanism can fail in a multi-protocol - environment. During the transition between IPv4 and IPv6, FTP needs - the ability to negotiate the network protocol that will be used for - data transfer. - - - -Allman, et. al. Standards Track [Page 1] - -RFC 2428 FTP Extensions for IPv6 and NATs September 1998 - - - This document provides a specification for a way that FTP can - communicate data connection endpoint information for network - protocols other than IPv4. In this specification, the FTP commands - PORT and PASV are replaced with EPRT and EPSV, respectively. This - document is organized as follows. Section 2 outlines the EPRT - command and Section 3 outlines the EPSV command. Section 4 defines - the utilization of these two new FTP commands. Section 5 briefly - presents security considerations. Finally, Section 6 provides - conclusions. - -2. The EPRT Command - - The EPRT command allows for the specification of an extended address - for the data connection. The extended address MUST consist of the - network protocol as well as the network and transport addresses. The - format of EPRT is: - - EPRT - - The EPRT command keyword MUST be followed by a single space (ASCII - 32). Following the space, a delimiter character () MUST be - specified. The delimiter character MUST be one of the ASCII - characters in range 33-126 inclusive. The character "|" (ASCII 124) - is recommended unless it coincides with a character needed to encode - the network address. - - The argument MUST be an address family number defined by - IANA in the latest Assigned Numbers RFC (RFC 1700 [RP94] as of the - writing of this document). This number indicates the protocol to be - used (and, implicitly, the address length). This document will use - two of address family numbers from [RP94] as examples, according to - the following table: - - AF Number Protocol - --------- -------- - 1 Internet Protocol, Version 4 [Pos81a] - 2 Internet Protocol, Version 6 [DH96] - - The is a protocol specific string representation of the - network address. For the two address families specified above (AF - Number 1 and 2), addresses MUST be in the following format: - - AF Number Address Format Example - --------- -------------- ------- - 1 dotted decimal 132.235.1.2 - 2 IPv6 string 1080::8:800:200C:417A - representations - defined in [HD96] - - - -Allman, et. al. Standards Track [Page 2] - -RFC 2428 FTP Extensions for IPv6 and NATs September 1998 - - - The argument must be the string representation of the - number of the TCP port on which the host is listening for the data - connection. - - The following are sample EPRT commands: - - EPRT |1|132.235.1.2|6275| - - EPRT |2|1080::8:800:200C:417A|5282| - - The first command specifies that the server should use IPv4 to open a - data connection to the host "132.235.1.2" on TCP port 6275. The - second command specifies that the server should use the IPv6 network - protocol and the network address "1080::8:800:200C:417A" to open a - TCP data connection on port 5282. - - Upon receipt of a valid EPRT command, the server MUST return a code - of 200 (Command OK). The standard negative error code 500 and 501 - [PR85] are sufficient to handle most errors (e.g., syntax errors) - involving the EPRT command. However, an additional error code is - needed. The response code 522 indicates that the server does not - support the requested network protocol. The interpretation of this - new error code is: - - 5yz Negative Completion - x2z Connections - xy2 Extended Port Failure - unknown network protocol - - The text portion of the response MUST indicate which network - protocols the server does support. If the network protocol is - unsupported, the format of the response string MUST be: - - \ - (prot1,prot2,...,protn) - - Both the numeric code specified above and the protocol information - between the characters '(' and ')' are intended for the software - automata receiving the response; the textual message between the - numeric code and the '(' is intended for the human user and can be - any arbitrary text, but MUST NOT include the characters '(' and ')'. - In the above case, the text SHOULD indicate that the network protocol - in the EPRT command is not supported by the server. The list of - protocols inside the parenthesis MUST be a comma separated list of - address family numbers. Two example response strings follow: - - Network protocol not supported, use (1) - - Network protocol not supported, use (1,2) - - - -Allman, et. al. Standards Track [Page 3] - -RFC 2428 FTP Extensions for IPv6 and NATs September 1998 - - -3. The EPSV Command - - The EPSV command requests that a server listen on a data port and - wait for a connection. The EPSV command takes an optional argument. - The response to this command includes only the TCP port number of the - listening connection. The format of the response, however, is - similar to the argument of the EPRT command. This allows the same - parsing routines to be used for both commands. In addition, the - format leaves a place holder for the network protocol and/or network - address, which may be needed in the EPSV response in the future. The - response code for entering passive mode using an extended address - MUST be 229. The interpretation of this code, according to [PR85] - is: - - 2yz Positive Completion - x2z Connections - xy9 Extended Passive Mode Entered - - The text returned in response to the EPSV command MUST be: - - \ - () - - The portion of the string enclosed in parentheses MUST be the exact - string needed by the EPRT command to open the data connection, as - specified above. - - The first two fields contained in the parenthesis MUST be blank. The - third field MUST be the string representation of the TCP port number - on which the server is listening for a data connection. The network - protocol used by the data connection will be the same network - protocol used by the control connection. In addition, the network - address used to establish the data connection will be the same - network address used for the control connection. An example response - string follows: - - Entering Extended Passive Mode (|||6446|) - - The standard negative error codes 500 and 501 are sufficient to - handle all errors involving the EPSV command (e.g., syntax errors). - - When the EPSV command is issued with no argument, the server will - choose the network protocol for the data connection based on the - protocol used for the control connection. However, in the case of - proxy FTP, this protocol might not be appropriate for communication - between the two servers. Therefore, the client needs to be able to - request a specific protocol. If the server returns a protocol that - is not supported by the host that will be connecting to the port, the - - - -Allman, et. al. Standards Track [Page 4] - -RFC 2428 FTP Extensions for IPv6 and NATs September 1998 - - - client MUST issue an ABOR (abort) command to allow the server to - close down the listening connection. The client can then send an - EPSV command requesting the use of a specific network protocol, as - follows: - - EPSV - - If the requested protocol is supported by the server, it SHOULD use - the protocol. If not, the server MUST return the 522 error messages - as outlined in section 2. - - Finally, the EPSV command can be used with the argument "ALL" to - inform Network Address Translators that the EPRT command (as well as - other data commands) will no longer be used. An example of this - command follows: - - EPSVALL - - Upon receipt of an EPSV ALL command, the server MUST reject all data - connection setup commands other than EPSV (i.e., EPRT, PORT, PASV, et - al.). This use of the EPSV command is further explained in section - 4. - -4. Command Usage - - For all FTP transfers where the control and data connection(s) are - being established between the same two machines, the EPSV command - MUST be used. Using the EPSV command benefits performance of - transfers that traverse firewalls or Network Address Translators - (NATs). RFC 1579 [Bel94] recommends using the passive command when - behind firewalls since firewalls do not generally allow incoming - connections (which are required when using the PORT (EPRT) command). - In addition, using EPSV as defined in this document does not require - NATs to change the network address in the traffic as it is forwarded. - The NAT would have to change the address if the EPRT command was - used. Finally, if the client issues an "EPSV ALL" command, NATs may - be able to put the connection on a "fast path" through the - translator, as the EPRT command will never be used and therefore, - translation of the data portion of the segments will never be needed. - When a client only expects to do two-way FTP transfers, it SHOULD - issue this command as soon as possible. If a client later finds that - it must do a three-way FTP transfer after issuing an EPSV ALL - command, a new FTP session MUST be started. - - - - - - - - -Allman, et. al. Standards Track [Page 5] - -RFC 2428 FTP Extensions for IPv6 and NATs September 1998 - - -5. Security Issues - - The authors do not believe that these changes to FTP introduce new - security problems. A companion Work in Progress [AO98] is a more - general discussion of FTP security issues and techniques to reduce - these security problems. - -6. Conclusions - - The extensions specified in this paper will enable FTP to operate - over a variety of network protocols. - -References - - [AO98] Allman, M., and S. Ostermann, "FTP Security - Considerations", Work in Progress. - - [Bel94] Bellovin, S., "Firewall-Friendly FTP", RFC 1579, February - 1994. - - [Bra97] Bradner, S., "Key words for use in RFCs to Indicate - Requirement Levels", BCP 14, RFC 2119, March 1997. - - [DH96] Deering, S., and R. Hinden, "Internet Protocol, Version 6 - (IPv6) Specification", RFC 1883, December 1995. - - [HD96] Hinden, R., and S. Deering, "IP Version 6 Addressing - Architecture", RFC 2373, July 1998. - - [Pis94] Piscitello, D., "FTP Operation Over Big Address Records - (FOOBAR)", RFC 1639, June 1994. - - [Pos81a] Postel, J., "Internet Protocol", STD 5, RFC 791, September - 1981. - - [Pos81b] Postel, J., "Transmission Control Protocol", STD 7, RFC 793, - September 1981. - - [PR85] Postel, J., and J. Reynolds, "File Transfer Protocol (FTP)", - STD 9, RFC 959, October 1985. - - [RP94] Reynolds, J., and J. Postel, "Assigned Numbers", STD 2, RFC - 1700, October 1994. See also: - http://www.iana.org/numbers.html - - - - - - - -Allman, et. al. Standards Track [Page 6] - -RFC 2428 FTP Extensions for IPv6 and NATs September 1998 - - -Authors' Addresses - - Mark Allman - NASA Lewis Research Center/Sterling Software - 21000 Brookpark Rd. MS 54-2 - Cleveland, OH 44135 - - Phone: (216) 433-6586 - EMail: mallman@lerc.nasa.gov - http://gigahertz.lerc.nasa.gov/~mallman/ - - - Shawn Ostermann - School of Electrical Engineering and Computer Science - Ohio University - 416 Morton Hall - Athens, OH 45701 - - Phone: (740) 593-1234 - EMail: ostermann@cs.ohiou.edu - - - Craig Metz - The Inner Net - Box 10314-1954 - Blacksburg, VA 24062-0314 - - Phone: (DSN) 754-8590 - EMail: cmetz@inner.net - - - - - - - - - - - - - - - - - - - - - - -Allman, et. al. Standards Track [Page 7] - -RFC 2428 FTP Extensions for IPv6 and NATs September 1998 - - -Full Copyright Statement - - Copyright (C) The Internet Society (1998). All Rights Reserved. - - This document and translations of it may be copied and furnished to - others, and derivative works that comment on or otherwise explain it - or assist in its implementation may be prepared, copied, published - and distributed, in whole or in part, without restriction of any - kind, provided that the above copyright notice and this paragraph are - included on all such copies and derivative works. However, this - document itself may not be modified in any way, such as by removing - the copyright notice or references to the Internet Society or other - Internet organizations, except as needed for the purpose of - developing Internet standards in which case the procedures for - copyrights defined in the Internet Standards process must be - followed, or as required to translate it into languages other than - English. - - The limited permissions granted above are perpetual and will not be - revoked by the Internet Society or its successors or assigns. - - This document and the information contained herein is provided on an - "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING - TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING - BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION - HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF - MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. - - - - - - - - - - - - - - - - - - - - - - - - -Allman, et. al. Standards Track [Page 8] - diff --git a/lib/inets/doc/archive/rfc2577.txt b/lib/inets/doc/archive/rfc2577.txt deleted file mode 100644 index 83ba203130..0000000000 --- a/lib/inets/doc/archive/rfc2577.txt +++ /dev/null @@ -1,451 +0,0 @@ - - - - - - -Network Working Group M. Allman -Request for Comments: 2577 NASA Glenn/Sterling Software -Category: Informational S. Ostermann - Ohio University - May 1999 - - - FTP Security Considerations - -Status of this Memo - - This memo provides information for the Internet community. It does - not specify an Internet standard of any kind. Distribution of this - memo is unlimited. - -Copyright Notice - - Copyright (C) The Internet Society (1999). All Rights Reserved. - -Abstract - - The specification for the File Transfer Protocol (FTP) contains a - number of mechanisms that can be used to compromise network security. - The FTP specification allows a client to instruct a server to - transfer files to a third machine. This third-party mechanism, known - as proxy FTP, causes a well known security problem. The FTP - specification also allows an unlimited number of attempts at entering - a user's password. This allows brute force "password guessing" - attacks. This document provides suggestions for system - administrators and those implementing FTP servers that will decrease - the security problems associated with FTP. - -1 Introduction - - The File Transfer Protocol specification (FTP) [PR85] provides a - mechanism that allows a client to establish an FTP control connection - and transfer a file between two FTP servers. This "proxy FTP" - mechanism can be used to decrease the amount of traffic on the - network; the client instructs one server to transfer a file to - another server, rather than transferring the file from the first - server to the client and then from the client to the second server. - This is particularly useful when the client connects to the network - using a slow link (e.g., a modem). While useful, proxy FTP provides - a security problem known as a "bounce attack" [CERT97:27]. In - addition to the bounce attack, FTP servers can be used by attackers - to guess passwords using brute force. - - - - - -Allman & Ostermann Informational [Page 1] - -RFC 2577 FTP Security Considerations May 1999 - - - This document does not contain a discussion of FTP when used in - conjunction with strong security protocols, such as IP Security. - These security concerns should be documented, however they are out of - the scope of this document. - - This paper provides information for FTP server implementers and - system administrators, as follows. Section 2 describes the FTP - "bounce attack". Section 3 provides suggestions for minimizing the - bounce attack. Section 4 provides suggestions for servers which - limit access based on network address. Section 5 provides - recommendations for limiting brute force "password guessing" by - clients. Next, section 6 provides a brief discussion of mechanisms - to improve privacy. Section 7 provides a mechanism to prevent user - identity guessing. Section 8 discusses the practice of port - stealing. Finally, section 9 provides an overview of other FTP - security issues related to software bugs rather than protocol issues. - -2 The Bounce Attack - - The version of FTP specified in the standard [PR85] provides a method - for attacking well known network servers, while making the - perpetrators difficult to track down. The attack involves sending an - FTP "PORT" command to an FTP server containing the network address - and the port number of the machine and service being attacked. At - this point, the original client can instruct the FTP server to send a - file to the service being attacked. Such a file would contain - commands relevant to the service being attacked (SMTP, NNTP, etc.). - Instructing a third party to connect to the service, rather than - connecting directly, makes tracking down the perpetrator difficult - and can circumvent network-address-based access restrictions. - - As an example, a client uploads a file containing SMTP commands to an - FTP server. Then, using an appropriate PORT command, the client - instructs the server to open a connection to a third machine's SMTP - port. Finally, the client instructs the server to transfer the - uploaded file containing SMTP commands to the third machine. This - may allow the client to forge mail on the third machine without - making a direct connection. This makes it difficult to track - attackers. - -3 Protecting Against the Bounce Attack - - The original FTP specification [PR85] assumes that data connections - will be made using the Transmission Control Protocol (TCP) [Pos81]. - TCP port numbers in the range 0 - 1023 are reserved for well known - services such as mail, network news and FTP control connections - [RP94]. The FTP specification makes no restrictions on the TCP port - number used for the data connection. Therefore, using proxy FTP, - - - -Allman & Ostermann Informational [Page 2] - -RFC 2577 FTP Security Considerations May 1999 - - - clients have the ability to tell the server to attack a well known - service on any machine. - - To avoid such bounce attacks, it is suggested that servers not open - data connections to TCP ports less than 1024. If a server receives a - PORT command containing a TCP port number less than 1024, the - suggested response is 504 (defined as "Command not implemented for - that parameter" by [PR85]). Note that this still leaves non-well - known servers (those running on ports greater than 1023) vulnerable - to bounce attacks. - - Several proposals (e.g., [AOM98] and [Pis94]) provide a mechanism - that would allow data connections to be made using a transport - protocol other than TCP. Similar precautions should be taken to - protect well known services when using these protocols. - - Also note that the bounce attack generally requires that a - perpetrator be able to upload a file to an FTP server and later - download it to the service being attacked. Using proper file - protections will prevent this behavior. However, attackers can also - attack services by sending random data from a remote FTP server which - may cause problems for some services. - - Disabling the PORT command is also an option for protecting against - the bounce attack. Most file transfers can be made using only the - PASV command [Bel94]. The disadvantage of disabling the PORT command - is that one loses the ability to use proxy FTP, but proxy FTP may not - be necessary in a particular environment. - -4 Restricted Access - - For some FTP servers, it is desirable to restrict access based on - network address. For example, a server might want to restrict access - to certain files from certain places (e.g., a certain file should not - be transferred out of an organization). In such a situation, the - server should confirm that the network address of the remote hosts on - both the control connection and the data connection are within the - organization before sending a restricted file. By checking both - connections, a server is protected against the case when the control - connection is established with a trusted host and the data connection - is not. Likewise, the client should verify the IP address of the - remote host after accepting a connection on a port opened in listen - mode to verify that the connection was made by the expected server. - - Note that restricting access based on network address leaves the FTP - server vulnerable to "spoof" attacks. In a spoof attack, for - example, an attacking machine could assume the host address of - another machine inside an organization and download files that are - - - -Allman & Ostermann Informational [Page 3] - -RFC 2577 FTP Security Considerations May 1999 - - - not accessible from outside the organization. Whenever possible, - secure authentication mechanisms should be used, such as those - outlined in [HL97]. - -5 Protecting Passwords - - To minimize the risk of brute force password guessing through the FTP - server, it is suggested that servers limit the number of attempts - that can be made at sending a correct password. After a small number - of attempts (3-5), the server should close the control connection - with the client. Before closing the control connection the server - must send a return code of 421 ("Service not available, closing - control connection." [PR85]) to the client. In addition, it is - suggested that the server impose a 5 second delay before replying to - an invalid "PASS" command to diminish the efficiency of a brute force - attack. If available, mechanisms already provided by the target - operating system should be used to implement the above suggestions. - - An intruder can subvert the above mechanisms by establishing - multiple, parallel control connections to a server. To combat the - use of multiple concurrent connections, the server could either limit - the total number of control connections possible or attempt to detect - suspicious activity across sessions and refuse further connections - from the site. However, both of these mechanisms open the door to - "denial of service" attacks, in which an attacker purposely initiates - the attack to disable access by a valid user. - - Standard FTP [PR85] sends passwords in clear text using the "PASS" - command. It is suggested that FTP clients and servers use alternate - authentication mechanisms that are not subject to eavesdropping (such - as the mechanisms being developed by the IETF Common Authentication - Technology Working Group [HL97]). - -6 Privacy - - All data and control information (including passwords) is sent across - the network in unencrypted form by standard FTP [PR85]. To guarantee - the privacy of the information FTP transmits, a strong encryption - scheme should be used whenever possible. One such mechanism is - defined in [HL97]. - -7 Protecting Usernames - - Standard FTP [PR85] specifies a 530 response to the USER command when - the username is rejected. If the username is valid and a password is - required FTP returns a 331 response instead. In order to prevent a - malicious client from determining valid usernames on a server, it is - suggested that a server always return 331 to the USER command and - - - -Allman & Ostermann Informational [Page 4] - -RFC 2577 FTP Security Considerations May 1999 - - - then reject the combination of username and password for an invalid - username. - -8 Port Stealing - - Many operating systems assign dynamic port numbers in increasing - order. By making a legitimate transfer, an attacker can observe the - current port number allocated by the server and "guess" the next one - that will be used. The attacker can make a connection to this port, - thus denying another legitimate client the ability to make a - transfer. Alternatively, the attacker can steal a file meant for a - legitimate user. In addition, an attacker can insert a forged file - into a data stream thought to come from an authenticated client. - This problem can be mitigated by making FTP clients and servers use - random local port numbers for data connections, either by requesting - random ports from the operating system or using system dependent - mechanisms. - -9 Software-Base Security Problems - - The emphasis in this document is on protocol-related security issues. - There are a number of documented FTP security-related problems that - are due to poor implementation as well. Although the details of - these types of problems are beyond the scope of this document, it - should be pointed out that the following FTP features has been abused - in the past and should be treated with great care by future - implementers: - - Anonymous FTP - - Anonymous FTP refers to the ability of a client to connect to an - FTP server with minimal authentication and gain access to public - files. Security problems arise when such a user can read all - files on the system or can create files. [CERT92:09] [CERT93:06] - - Remote Command Execution - - An optional FTP extension, "SITE EXEC", allows clients to execute - arbitrary commands on the server. This feature should obviously - be implemented with great care. There are several documented - cases of the FTP "SITE EXEC" command being used to subvert server - security [CERT94:08] [CERT95:16] - - Debug Code - - Several previous security compromises related to FTP can be - attributed to software that was installed with debugging features - enabled [CERT88:01]. - - - -Allman & Ostermann Informational [Page 5] - -RFC 2577 FTP Security Considerations May 1999 - - - This document recommends that implementors of FTP servers with these - capabilities review all of the CERT advisories for attacks on these - or similar mechanisms before releasing their software. - -10 Conclusion - - Using the above suggestions can decrease the security problems - associated with FTP servers without eliminating functionality. - -11 Security Considerations - - Security issues are discussed throughout this memo. - -Acknowledgments - - We would like to thank Alex Belits, Jim Bound, William Curtin, Robert - Elz, Paul Hethmon, Alun Jones and Stephen Tihor for their helpful - comments on this paper. Also, we thank the FTPEXT WG members who - gave many useful suggestions at the Memphis IETF meeting. - -References - - [AOM98] Allman, M., Ostermann, S. and C. Metz, "FTP Extensions - for IPv6 and NATs", RFC 2428, September 1998. - - [Bel94] Bellovin. S., "Firewall-Friendly FTP", RFC 1579, February - 1994. - - [CERT88:01] CERT Advisory CA-88:01. ftpd Vulnerability. December, - 1988 ftp://info.cert.org/pub/cert_advisories/ - - [CERT92:09] CERT Advisory CA-92:09. AIX Anonymous FTP Vulnerability. - April 27, 1992. ftp://info.cert.org/pub/cert_advisories/ - - [CERT93:06] CERT Advisory CA-93:06. Wuarchive ftpd Vulnerability. - September 19,1997 - ftp://info.cert.org/pub/cert_advisories/ - - [CERT94:08] CERT Advisory CA-94:08. ftpd Vulnerabilities. September - 23, 1997. ftp://info.cert.org/pub/cert_advisories/ - - [CERT95:16] CERT Advisory CA-95:16. wu-ftpd Misconfiguration - Vulnerability. September 23, 1997 - ftp://info.cert.org/pub/cert_advisories/ - - [CERT97:27] CERT Advisory CA-97.27. FTP Bounce. January 8, 1998. - ftp://info.cert.org/pub/cert_advisories/ - - - - -Allman & Ostermann Informational [Page 6] - -RFC 2577 FTP Security Considerations May 1999 - - - [HL97] Horowitz, M. and S. Lunt, "FTP Security Extensions", RFC - 2228, October 1997. - - [Pis94] Piscitello, D., "FTP Operation Over Big Address Records - (FOOBAR), RFC 1639, June 1994. - - [Pos81] Postel, J., "Transmission Control Protocol", STD 7, RFC - 793, September 1981. - - [PR85] Postel, J. and J. Reynolds, "File Transfer Protocol - (FTP)", STD 9, RFC 959, October 1985. - - [RP94] Reynolds, J. and J. Postel, "Assigned Numbers", STD 2, - RFC 1700, October 1994. See also: - http://www.iana.org/numbers.html - -Authors' Addresses - - Mark Allman - NASA Glenn Research Center/Sterling Software - 21000 Brookpark Rd. MS 54-2 - Cleveland, OH 44135 - - EMail: mallman@grc.nasa.gov - - - Shawn Ostermann - School of Electrical Engineering and Computer Science - Ohio University - 416 Morton Hall - Athens, OH 45701 - - EMail: ostermann@cs.ohiou.edu - - - - - - - - - - - - - - - - - - -Allman & Ostermann Informational [Page 7] - -RFC 2577 FTP Security Considerations May 1999 - - -Full Copyright Statement - - Copyright (C) The Internet Society (1999). All Rights Reserved. - - This document and translations of it may be copied and furnished to - others, and derivative works that comment on or otherwise explain it - or assist in its implementation may be prepared, copied, published - and distributed, in whole or in part, without restriction of any - kind, provided that the above copyright notice and this paragraph are - included on all such copies and derivative works. However, this - document itself may not be modified in any way, such as by removing - the copyright notice or references to the Internet Society or other - Internet organizations, except as needed for the purpose of - developing Internet standards in which case the procedures for - copyrights defined in the Internet Standards process must be - followed, or as required to translate it into languages other than - English. - - The limited permissions granted above are perpetual and will not be - revoked by the Internet Society or its successors or assigns. - - This document and the information contained herein is provided on an - "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING - TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING - BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION - HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF - MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. - -Acknowledgement - - Funding for the RFC Editor function is currently provided by the - Internet Society. - - - - - - - - - - - - - - - - - - - -Allman & Ostermann Informational [Page 8] - diff --git a/lib/inets/doc/archive/rfc959.txt b/lib/inets/doc/archive/rfc959.txt deleted file mode 100644 index 5c9f11af5d..0000000000 --- a/lib/inets/doc/archive/rfc959.txt +++ /dev/null @@ -1,3933 +0,0 @@ - - -Network Working Group J. Postel -Request for Comments: 959 J. Reynolds - ISI -Obsoletes RFC: 765 (IEN 149) October 1985 - - FILE TRANSFER PROTOCOL (FTP) - - -Status of this Memo - - This memo is the official specification of the File Transfer - Protocol (FTP). Distribution of this memo is unlimited. - - The following new optional commands are included in this edition of - the specification: - - CDUP (Change to Parent Directory), SMNT (Structure Mount), STOU - (Store Unique), RMD (Remove Directory), MKD (Make Directory), PWD - (Print Directory), and SYST (System). - - Note that this specification is compatible with the previous edition. - -1. INTRODUCTION - - The objectives of FTP are 1) to promote sharing of files (computer - programs and/or data), 2) to encourage indirect or implicit (via - programs) use of remote computers, 3) to shield a user from - variations in file storage systems among hosts, and 4) to transfer - data reliably and efficiently. FTP, though usable directly by a user - at a terminal, is designed mainly for use by programs. - - The attempt in this specification is to satisfy the diverse needs of - users of maxi-hosts, mini-hosts, personal workstations, and TACs, - with a simple, and easily implemented protocol design. - - This paper assumes knowledge of the Transmission Control Protocol - (TCP) [2] and the Telnet Protocol [3]. These documents are contained - in the ARPA-Internet protocol handbook [1]. - -2. OVERVIEW - - In this section, the history, the terminology, and the FTP model are - discussed. The terms defined in this section are only those that - have special significance in FTP. Some of the terminology is very - specific to the FTP model; some readers may wish to turn to the - section on the FTP model while reviewing the terminology. - - - - - - - -Postel & Reynolds [Page 1] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 2.1. HISTORY - - FTP has had a long evolution over the years. Appendix III is a - chronological compilation of Request for Comments documents - relating to FTP. These include the first proposed file transfer - mechanisms in 1971 that were developed for implementation on hosts - at M.I.T. (RFC 114), plus comments and discussion in RFC 141. - - RFC 172 provided a user-level oriented protocol for file transfer - between host computers (including terminal IMPs). A revision of - this as RFC 265, restated FTP for additional review, while RFC 281 - suggested further changes. The use of a "Set Data Type" - transaction was proposed in RFC 294 in January 1982. - - RFC 354 obsoleted RFCs 264 and 265. The File Transfer Protocol - was now defined as a protocol for file transfer between HOSTs on - the ARPANET, with the primary function of FTP defined as - transfering files efficiently and reliably among hosts and - allowing the convenient use of remote file storage capabilities. - RFC 385 further commented on errors, emphasis points, and - additions to the protocol, while RFC 414 provided a status report - on the working server and user FTPs. RFC 430, issued in 1973, - (among other RFCs too numerous to mention) presented further - comments on FTP. Finally, an "official" FTP document was - published as RFC 454. - - By July 1973, considerable changes from the last versions of FTP - were made, but the general structure remained the same. RFC 542 - was published as a new "official" specification to reflect these - changes. However, many implementations based on the older - specification were not updated. - - In 1974, RFCs 607 and 614 continued comments on FTP. RFC 624 - proposed further design changes and minor modifications. In 1975, - RFC 686 entitled, "Leaving Well Enough Alone", discussed the - differences between all of the early and later versions of FTP. - RFC 691 presented a minor revision of RFC 686, regarding the - subject of print files. - - Motivated by the transition from the NCP to the TCP as the - underlying protocol, a phoenix was born out of all of the above - efforts in RFC 765 as the specification of FTP for use on TCP. - - This current edition of the FTP specification is intended to - correct some minor documentation errors, to improve the - explanation of some protocol features, and to add some new - optional commands. - - -Postel & Reynolds [Page 2] - - - -RFC 959 October 1985 -File Transfer Protocol - - - In particular, the following new optional commands are included in - this edition of the specification: - - CDUP - Change to Parent Directory - - SMNT - Structure Mount - - STOU - Store Unique - - RMD - Remove Directory - - MKD - Make Directory - - PWD - Print Directory - - SYST - System - - This specification is compatible with the previous edition. A - program implemented in conformance to the previous specification - should automatically be in conformance to this specification. - - 2.2. TERMINOLOGY - - ASCII - - The ASCII character set is as defined in the ARPA-Internet - Protocol Handbook. In FTP, ASCII characters are defined to be - the lower half of an eight-bit code set (i.e., the most - significant bit is zero). - - access controls - - Access controls define users' access privileges to the use of a - system, and to the files in that system. Access controls are - necessary to prevent unauthorized or accidental use of files. - It is the prerogative of a server-FTP process to invoke access - controls. - - byte size - - There are two byte sizes of interest in FTP: the logical byte - size of the file, and the transfer byte size used for the - transmission of the data. The transfer byte size is always 8 - bits. The transfer byte size is not necessarily the byte size - in which data is to be stored in a system, nor the logical byte - size for interpretation of the structure of the data. - - - -Postel & Reynolds [Page 3] - - - -RFC 959 October 1985 -File Transfer Protocol - - - control connection - - The communication path between the USER-PI and SERVER-PI for - the exchange of commands and replies. This connection follows - the Telnet Protocol. - - data connection - - A full duplex connection over which data is transferred, in a - specified mode and type. The data transferred may be a part of - a file, an entire file or a number of files. The path may be - between a server-DTP and a user-DTP, or between two - server-DTPs. - - data port - - The passive data transfer process "listens" on the data port - for a connection from the active transfer process in order to - open the data connection. - - DTP - - The data transfer process establishes and manages the data - connection. The DTP can be passive or active. - - End-of-Line - - The end-of-line sequence defines the separation of printing - lines. The sequence is Carriage Return, followed by Line Feed. - - EOF - - The end-of-file condition that defines the end of a file being - transferred. - - EOR - - The end-of-record condition that defines the end of a record - being transferred. - - error recovery - - A procedure that allows a user to recover from certain errors - such as failure of either host system or transfer process. In - FTP, error recovery may involve restarting a file transfer at a - given checkpoint. - - - -Postel & Reynolds [Page 4] - - - -RFC 959 October 1985 -File Transfer Protocol - - - FTP commands - - A set of commands that comprise the control information flowing - from the user-FTP to the server-FTP process. - - file - - An ordered set of computer data (including programs), of - arbitrary length, uniquely identified by a pathname. - - mode - - The mode in which data is to be transferred via the data - connection. The mode defines the data format during transfer - including EOR and EOF. The transfer modes defined in FTP are - described in the Section on Transmission Modes. - - NVT - - The Network Virtual Terminal as defined in the Telnet Protocol. - - NVFS - - The Network Virtual File System. A concept which defines a - standard network file system with standard commands and - pathname conventions. - - page - - A file may be structured as a set of independent parts called - pages. FTP supports the transmission of discontinuous files as - independent indexed pages. - - pathname - - Pathname is defined to be the character string which must be - input to a file system by a user in order to identify a file. - Pathname normally contains device and/or directory names, and - file name specification. FTP does not yet specify a standard - pathname convention. Each user must follow the file naming - conventions of the file systems involved in the transfer. - - PI - - The protocol interpreter. The user and server sides of the - protocol have distinct roles implemented in a user-PI and a - server-PI. - - -Postel & Reynolds [Page 5] - - - -RFC 959 October 1985 -File Transfer Protocol - - - record - - A sequential file may be structured as a number of contiguous - parts called records. Record structures are supported by FTP - but a file need not have record structure. - - reply - - A reply is an acknowledgment (positive or negative) sent from - server to user via the control connection in response to FTP - commands. The general form of a reply is a completion code - (including error codes) followed by a text string. The codes - are for use by programs and the text is usually intended for - human users. - - server-DTP - - The data transfer process, in its normal "active" state, - establishes the data connection with the "listening" data port. - It sets up parameters for transfer and storage, and transfers - data on command from its PI. The DTP can be placed in a - "passive" state to listen for, rather than initiate a - connection on the data port. - - server-FTP process - - A process or set of processes which perform the function of - file transfer in cooperation with a user-FTP process and, - possibly, another server. The functions consist of a protocol - interpreter (PI) and a data transfer process (DTP). - - server-PI - - The server protocol interpreter "listens" on Port L for a - connection from a user-PI and establishes a control - communication connection. It receives standard FTP commands - from the user-PI, sends replies, and governs the server-DTP. - - type - - The data representation type used for data transfer and - storage. Type implies certain transformations between the time - of data storage and data transfer. The representation types - defined in FTP are described in the Section on Establishing - Data Connections. - - - - -Postel & Reynolds [Page 6] - - - -RFC 959 October 1985 -File Transfer Protocol - - - user - - A person or a process on behalf of a person wishing to obtain - file transfer service. The human user may interact directly - with a server-FTP process, but use of a user-FTP process is - preferred since the protocol design is weighted towards - automata. - - user-DTP - - The data transfer process "listens" on the data port for a - connection from a server-FTP process. If two servers are - transferring data between them, the user-DTP is inactive. - - user-FTP process - - A set of functions including a protocol interpreter, a data - transfer process and a user interface which together perform - the function of file transfer in cooperation with one or more - server-FTP processes. The user interface allows a local - language to be used in the command-reply dialogue with the - user. - - user-PI - - The user protocol interpreter initiates the control connection - from its port U to the server-FTP process, initiates FTP - commands, and governs the user-DTP if that process is part of - the file transfer. - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 7] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 2.3. THE FTP MODEL - - With the above definitions in mind, the following model (shown in - Figure 1) may be diagrammed for an FTP service. - - ------------- - |/---------\| - || User || -------- - ||Interface|<--->| User | - |\----^----/| -------- - ---------- | | | - |/------\| FTP Commands |/----V----\| - ||Server|<---------------->| User || - || PI || FTP Replies || PI || - |\--^---/| |\----^----/| - | | | | | | - -------- |/--V---\| Data |/----V----\| -------- - | File |<--->|Server|<---------------->| User |<--->| File | - |System| || DTP || Connection || DTP || |System| - -------- |\------/| |\---------/| -------- - ---------- ------------- - - Server-FTP USER-FTP - - NOTES: 1. The data connection may be used in either direction. - 2. The data connection need not exist all of the time. - - Figure 1 Model for FTP Use - - In the model described in Figure 1, the user-protocol interpreter - initiates the control connection. The control connection follows - the Telnet protocol. At the initiation of the user, standard FTP - commands are generated by the user-PI and transmitted to the - server process via the control connection. (The user may - establish a direct control connection to the server-FTP, from a - TAC terminal for example, and generate standard FTP commands - independently, bypassing the user-FTP process.) Standard replies - are sent from the server-PI to the user-PI over the control - connection in response to the commands. - - The FTP commands specify the parameters for the data connection - (data port, transfer mode, representation type, and structure) and - the nature of file system operation (store, retrieve, append, - delete, etc.). The user-DTP or its designate should "listen" on - the specified data port, and the server initiate the data - connection and data transfer in accordance with the specified - parameters. It should be noted that the data port need not be in - - -Postel & Reynolds [Page 8] - - - -RFC 959 October 1985 -File Transfer Protocol - - - the same host that initiates the FTP commands via the control - connection, but the user or the user-FTP process must ensure a - "listen" on the specified data port. It ought to also be noted - that the data connection may be used for simultaneous sending and - receiving. - - In another situation a user might wish to transfer files between - two hosts, neither of which is a local host. The user sets up - control connections to the two servers and then arranges for a - data connection between them. In this manner, control information - is passed to the user-PI but data is transferred between the - server data transfer processes. Following is a model of this - server-server interaction. - - - Control ------------ Control - ---------->| User-FTP |<----------- - | | User-PI | | - | | "C" | | - V ------------ V - -------------- -------------- - | Server-FTP | Data Connection | Server-FTP | - | "A" |<---------------------->| "B" | - -------------- Port (A) Port (B) -------------- - - - Figure 2 - - The protocol requires that the control connections be open while - data transfer is in progress. It is the responsibility of the - user to request the closing of the control connections when - finished using the FTP service, while it is the server who takes - the action. The server may abort data transfer if the control - connections are closed without command. - - The Relationship between FTP and Telnet: - - The FTP uses the Telnet protocol on the control connection. - This can be achieved in two ways: first, the user-PI or the - server-PI may implement the rules of the Telnet Protocol - directly in their own procedures; or, second, the user-PI or - the server-PI may make use of the existing Telnet module in the - system. - - Ease of implementaion, sharing code, and modular programming - argue for the second approach. Efficiency and independence - - - -Postel & Reynolds [Page 9] - - - -RFC 959 October 1985 -File Transfer Protocol - - - argue for the first approach. In practice, FTP relies on very - little of the Telnet Protocol, so the first approach does not - necessarily involve a large amount of code. - -3. DATA TRANSFER FUNCTIONS - - Files are transferred only via the data connection. The control - connection is used for the transfer of commands, which describe the - functions to be performed, and the replies to these commands (see the - Section on FTP Replies). Several commands are concerned with the - transfer of data between hosts. These data transfer commands include - the MODE command which specify how the bits of the data are to be - transmitted, and the STRUcture and TYPE commands, which are used to - define the way in which the data are to be represented. The - transmission and representation are basically independent but the - "Stream" transmission mode is dependent on the file structure - attribute and if "Compressed" transmission mode is used, the nature - of the filler byte depends on the representation type. - - 3.1. DATA REPRESENTATION AND STORAGE - - Data is transferred from a storage device in the sending host to a - storage device in the receiving host. Often it is necessary to - perform certain transformations on the data because data storage - representations in the two systems are different. For example, - NVT-ASCII has different data storage representations in different - systems. DEC TOPS-20s's generally store NVT-ASCII as five 7-bit - ASCII characters, left-justified in a 36-bit word. IBM Mainframe's - store NVT-ASCII as 8-bit EBCDIC codes. Multics stores NVT-ASCII - as four 9-bit characters in a 36-bit word. It is desirable to - convert characters into the standard NVT-ASCII representation when - transmitting text between dissimilar systems. The sending and - receiving sites would have to perform the necessary - transformations between the standard representation and their - internal representations. - - A different problem in representation arises when transmitting - binary data (not character codes) between host systems with - different word lengths. It is not always clear how the sender - should send data, and the receiver store it. For example, when - transmitting 32-bit bytes from a 32-bit word-length system to a - 36-bit word-length system, it may be desirable (for reasons of - efficiency and usefulness) to store the 32-bit bytes - right-justified in a 36-bit word in the latter system. In any - case, the user should have the option of specifying data - representation and transformation functions. It should be noted - - - -Postel & Reynolds [Page 10] - - - -RFC 959 October 1985 -File Transfer Protocol - - - that FTP provides for very limited data type representations. - Transformations desired beyond this limited capability should be - performed by the user directly. - - 3.1.1. DATA TYPES - - Data representations are handled in FTP by a user specifying a - representation type. This type may implicitly (as in ASCII or - EBCDIC) or explicitly (as in Local byte) define a byte size for - interpretation which is referred to as the "logical byte size." - Note that this has nothing to do with the byte size used for - transmission over the data connection, called the "transfer - byte size", and the two should not be confused. For example, - NVT-ASCII has a logical byte size of 8 bits. If the type is - Local byte, then the TYPE command has an obligatory second - parameter specifying the logical byte size. The transfer byte - size is always 8 bits. - - 3.1.1.1. ASCII TYPE - - This is the default type and must be accepted by all FTP - implementations. It is intended primarily for the transfer - of text files, except when both hosts would find the EBCDIC - type more convenient. - - The sender converts the data from an internal character - representation to the standard 8-bit NVT-ASCII - representation (see the Telnet specification). The receiver - will convert the data from the standard form to his own - internal form. - - In accordance with the NVT standard, the sequence - should be used where necessary to denote the end of a line - of text. (See the discussion of file structure at the end - of the Section on Data Representation and Storage.) - - Using the standard NVT-ASCII representation means that data - must be interpreted as 8-bit bytes. - - The Format parameter for ASCII and EBCDIC types is discussed - below. - - - - - - - - -Postel & Reynolds [Page 11] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 3.1.1.2. EBCDIC TYPE - - This type is intended for efficient transfer between hosts - which use EBCDIC for their internal character - representation. - - For transmission, the data are represented as 8-bit EBCDIC - characters. The character code is the only difference - between the functional specifications of EBCDIC and ASCII - types. - - End-of-line (as opposed to end-of-record--see the discussion - of structure) will probably be rarely used with EBCDIC type - for purposes of denoting structure, but where it is - necessary the character should be used. - - 3.1.1.3. IMAGE TYPE - - The data are sent as contiguous bits which, for transfer, - are packed into the 8-bit transfer bytes. The receiving - site must store the data as contiguous bits. The structure - of the storage system might necessitate the padding of the - file (or of each record, for a record-structured file) to - some convenient boundary (byte, word or block). This - padding, which must be all zeros, may occur only at the end - of the file (or at the end of each record) and there must be - a way of identifying the padding bits so that they may be - stripped off if the file is retrieved. The padding - transformation should be well publicized to enable a user to - process a file at the storage site. - - Image type is intended for the efficient storage and - retrieval of files and for the transfer of binary data. It - is recommended that this type be accepted by all FTP - implementations. - - 3.1.1.4. LOCAL TYPE - - The data is transferred in logical bytes of the size - specified by the obligatory second parameter, Byte size. - The value of Byte size must be a decimal integer; there is - no default value. The logical byte size is not necessarily - the same as the transfer byte size. If there is a - difference in byte sizes, then the logical bytes should be - packed contiguously, disregarding transfer byte boundaries - and with any necessary padding at the end. - - - -Postel & Reynolds [Page 12] - - - -RFC 959 October 1985 -File Transfer Protocol - - - When the data reaches the receiving host, it will be - transformed in a manner dependent on the logical byte size - and the particular host. This transformation must be - invertible (i.e., an identical file can be retrieved if the - same parameters are used) and should be well publicized by - the FTP implementors. - - For example, a user sending 36-bit floating-point numbers to - a host with a 32-bit word could send that data as Local byte - with a logical byte size of 36. The receiving host would - then be expected to store the logical bytes so that they - could be easily manipulated; in this example putting the - 36-bit logical bytes into 64-bit double words should - suffice. - - In another example, a pair of hosts with a 36-bit word size - may send data to one another in words by using TYPE L 36. - The data would be sent in the 8-bit transmission bytes - packed so that 9 transmission bytes carried two host words. - - 3.1.1.5. FORMAT CONTROL - - The types ASCII and EBCDIC also take a second (optional) - parameter; this is to indicate what kind of vertical format - control, if any, is associated with a file. The following - data representation types are defined in FTP: - - A character file may be transferred to a host for one of - three purposes: for printing, for storage and later - retrieval, or for processing. If a file is sent for - printing, the receiving host must know how the vertical - format control is represented. In the second case, it must - be possible to store a file at a host and then retrieve it - later in exactly the same form. Finally, it should be - possible to move a file from one host to another and process - the file at the second host without undue trouble. A single - ASCII or EBCDIC format does not satisfy all these - conditions. Therefore, these types have a second parameter - specifying one of the following three formats: - - 3.1.1.5.1. NON PRINT - - This is the default format to be used if the second - (format) parameter is omitted. Non-print format must be - accepted by all FTP implementations. - - - - -Postel & Reynolds [Page 13] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The file need contain no vertical format information. If - it is passed to a printer process, this process may - assume standard values for spacing and margins. - - Normally, this format will be used with files destined - for processing or just storage. - - 3.1.1.5.2. TELNET FORMAT CONTROLS - - The file contains ASCII/EBCDIC vertical format controls - (i.e., , , , , ) which the printer - process will interpret appropriately. , in exactly - this sequence, also denotes end-of-line. - - 3.1.1.5.2. CARRIAGE CONTROL (ASA) - - The file contains ASA (FORTRAN) vertical format control - characters. (See RFC 740 Appendix C; and Communications - of the ACM, Vol. 7, No. 10, p. 606, October 1964.) In a - line or a record formatted according to the ASA Standard, - the first character is not to be printed. Instead, it - should be used to determine the vertical movement of the - paper which should take place before the rest of the - record is printed. - - The ASA Standard specifies the following control - characters: - - Character Vertical Spacing - - blank Move paper up one line - 0 Move paper up two lines - 1 Move paper to top of next page - + No movement, i.e., overprint - - Clearly there must be some way for a printer process to - distinguish the end of the structural entity. If a file - has record structure (see below) this is no problem; - records will be explicitly marked during transfer and - storage. If the file has no record structure, the - end-of-line sequence is used to separate printing lines, - but these format effectors are overridden by the ASA - controls. - - - - - - -Postel & Reynolds [Page 14] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 3.1.2. DATA STRUCTURES - - In addition to different representation types, FTP allows the - structure of a file to be specified. Three file structures are - defined in FTP: - - file-structure, where there is no internal structure and - the file is considered to be a - continuous sequence of data bytes, - - record-structure, where the file is made up of sequential - records, - - and page-structure, where the file is made up of independent - indexed pages. - - File-structure is the default to be assumed if the STRUcture - command has not been used but both file and record structures - must be accepted for "text" files (i.e., files with TYPE ASCII - or EBCDIC) by all FTP implementations. The structure of a file - will affect both the transfer mode of a file (see the Section - on Transmission Modes) and the interpretation and storage of - the file. - - The "natural" structure of a file will depend on which host - stores the file. A source-code file will usually be stored on - an IBM Mainframe in fixed length records but on a DEC TOPS-20 - as a stream of characters partitioned into lines, for example - by . If the transfer of files between such disparate - sites is to be useful, there must be some way for one site to - recognize the other's assumptions about the file. - - With some sites being naturally file-oriented and others - naturally record-oriented there may be problems if a file with - one structure is sent to a host oriented to the other. If a - text file is sent with record-structure to a host which is file - oriented, then that host should apply an internal - transformation to the file based on the record structure. - Obviously, this transformation should be useful, but it must - also be invertible so that an identical file may be retrieved - using record structure. - - In the case of a file being sent with file-structure to a - record-oriented host, there exists the question of what - criteria the host should use to divide the file into records - which can be processed locally. If this division is necessary, - the FTP implementation should use the end-of-line sequence, - - -Postel & Reynolds [Page 15] - - - -RFC 959 October 1985 -File Transfer Protocol - - - for ASCII, or for EBCDIC text files, as the - delimiter. If an FTP implementation adopts this technique, it - must be prepared to reverse the transformation if the file is - retrieved with file-structure. - - 3.1.2.1. FILE STRUCTURE - - File structure is the default to be assumed if the STRUcture - command has not been used. - - In file-structure there is no internal structure and the - file is considered to be a continuous sequence of data - bytes. - - 3.1.2.2. RECORD STRUCTURE - - Record structures must be accepted for "text" files (i.e., - files with TYPE ASCII or EBCDIC) by all FTP implementations. - - In record-structure the file is made up of sequential - records. - - 3.1.2.3. PAGE STRUCTURE - - To transmit files that are discontinuous, FTP defines a page - structure. Files of this type are sometimes known as - "random access files" or even as "holey files". In these - files there is sometimes other information associated with - the file as a whole (e.g., a file descriptor), or with a - section of the file (e.g., page access controls), or both. - In FTP, the sections of the file are called pages. - - To provide for various page sizes and associated - information, each page is sent with a page header. The page - header has the following defined fields: - - Header Length - - The number of logical bytes in the page header - including this byte. The minimum header length is 4. - - Page Index - - The logical page number of this section of the file. - This is not the transmission sequence number of this - page, but the index used to identify this page of the - file. - - -Postel & Reynolds [Page 16] - - - -RFC 959 October 1985 -File Transfer Protocol - - - Data Length - - The number of logical bytes in the page data. The - minimum data length is 0. - - Page Type - - The type of page this is. The following page types - are defined: - - 0 = Last Page - - This is used to indicate the end of a paged - structured transmission. The header length must - be 4, and the data length must be 0. - - 1 = Simple Page - - This is the normal type for simple paged files - with no page level associated control - information. The header length must be 4. - - 2 = Descriptor Page - - This type is used to transmit the descriptive - information for the file as a whole. - - 3 = Access Controlled Page - - This type includes an additional header field - for paged files with page level access control - information. The header length must be 5. - - Optional Fields - - Further header fields may be used to supply per page - control information, for example, per page access - control. - - All fields are one logical byte in length. The logical byte - size is specified by the TYPE command. See Appendix I for - further details and a specific case at the page structure. - - A note of caution about parameters: a file must be stored and - retrieved with the same parameters if the retrieved version is to - - - - -Postel & Reynolds [Page 17] - - - -RFC 959 October 1985 -File Transfer Protocol - - - be identical to the version originally transmitted. Conversely, - FTP implementations must return a file identical to the original - if the parameters used to store and retrieve a file are the same. - - 3.2. ESTABLISHING DATA CONNECTIONS - - The mechanics of transferring data consists of setting up the data - connection to the appropriate ports and choosing the parameters - for transfer. Both the user and the server-DTPs have a default - data port. The user-process default data port is the same as the - control connection port (i.e., U). The server-process default - data port is the port adjacent to the control connection port - (i.e., L-1). - - The transfer byte size is 8-bit bytes. This byte size is relevant - only for the actual transfer of the data; it has no bearing on - representation of the data within a host's file system. - - The passive data transfer process (this may be a user-DTP or a - second server-DTP) shall "listen" on the data port prior to - sending a transfer request command. The FTP request command - determines the direction of the data transfer. The server, upon - receiving the transfer request, will initiate the data connection - to the port. When the connection is established, the data - transfer begins between DTP's, and the server-PI sends a - confirming reply to the user-PI. - - Every FTP implementation must support the use of the default data - ports, and only the USER-PI can initiate a change to non-default - ports. - - It is possible for the user to specify an alternate data port by - use of the PORT command. The user may want a file dumped on a TAC - line printer or retrieved from a third party host. In the latter - case, the user-PI sets up control connections with both - server-PI's. One server is then told (by an FTP command) to - "listen" for a connection which the other will initiate. The - user-PI sends one server-PI a PORT command indicating the data - port of the other. Finally, both are sent the appropriate - transfer commands. The exact sequence of commands and replies - sent between the user-controller and the servers is defined in the - Section on FTP Replies. - - In general, it is the server's responsibility to maintain the data - connection--to initiate it and to close it. The exception to this - - - - -Postel & Reynolds [Page 18] - - - -RFC 959 October 1985 -File Transfer Protocol - - - is when the user-DTP is sending the data in a transfer mode that - requires the connection to be closed to indicate EOF. The server - MUST close the data connection under the following conditions: - - 1. The server has completed sending data in a transfer mode - that requires a close to indicate EOF. - - 2. The server receives an ABORT command from the user. - - 3. The port specification is changed by a command from the - user. - - 4. The control connection is closed legally or otherwise. - - 5. An irrecoverable error condition occurs. - - Otherwise the close is a server option, the exercise of which the - server must indicate to the user-process by either a 250 or 226 - reply only. - - 3.3. DATA CONNECTION MANAGEMENT - - Default Data Connection Ports: All FTP implementations must - support use of the default data connection ports, and only the - User-PI may initiate the use of non-default ports. - - Negotiating Non-Default Data Ports: The User-PI may specify a - non-default user side data port with the PORT command. The - User-PI may request the server side to identify a non-default - server side data port with the PASV command. Since a connection - is defined by the pair of addresses, either of these actions is - enough to get a different data connection, still it is permitted - to do both commands to use new ports on both ends of the data - connection. - - Reuse of the Data Connection: When using the stream mode of data - transfer the end of the file must be indicated by closing the - connection. This causes a problem if multiple files are to be - transfered in the session, due to need for TCP to hold the - connection record for a time out period to guarantee the reliable - communication. Thus the connection can not be reopened at once. - - There are two solutions to this problem. The first is to - negotiate a non-default port. The second is to use another - transfer mode. - - A comment on transfer modes. The stream transfer mode is - - -Postel & Reynolds [Page 19] - - - -RFC 959 October 1985 -File Transfer Protocol - - - inherently unreliable, since one can not determine if the - connection closed prematurely or not. The other transfer modes - (Block, Compressed) do not close the connection to indicate the - end of file. They have enough FTP encoding that the data - connection can be parsed to determine the end of the file. - Thus using these modes one can leave the data connection open - for multiple file transfers. - - 3.4. TRANSMISSION MODES - - The next consideration in transferring data is choosing the - appropriate transmission mode. There are three modes: one which - formats the data and allows for restart procedures; one which also - compresses the data for efficient transfer; and one which passes - the data with little or no processing. In this last case the mode - interacts with the structure attribute to determine the type of - processing. In the compressed mode, the representation type - determines the filler byte. - - All data transfers must be completed with an end-of-file (EOF) - which may be explicitly stated or implied by the closing of the - data connection. For files with record structure, all the - end-of-record markers (EOR) are explicit, including the final one. - For files transmitted in page structure a "last-page" page type is - used. - - NOTE: In the rest of this section, byte means "transfer byte" - except where explicitly stated otherwise. - - For the purpose of standardized transfer, the sending host will - translate its internal end of line or end of record denotation - into the representation prescribed by the transfer mode and file - structure, and the receiving host will perform the inverse - translation to its internal denotation. An IBM Mainframe record - count field may not be recognized at another host, so the - end-of-record information may be transferred as a two byte control - code in Stream mode or as a flagged bit in a Block or Compressed - mode descriptor. End-of-line in an ASCII or EBCDIC file with no - record structure should be indicated by or , - respectively. Since these transformations imply extra work for - some systems, identical systems transferring non-record structured - text files might wish to use a binary representation and stream - mode for the transfer. - - - - - - -Postel & Reynolds [Page 20] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The following transmission modes are defined in FTP: - - 3.4.1. STREAM MODE - - The data is transmitted as a stream of bytes. There is no - restriction on the representation type used; record structures - are allowed. - - In a record structured file EOR and EOF will each be indicated - by a two-byte control code. The first byte of the control code - will be all ones, the escape character. The second byte will - have the low order bit on and zeros elsewhere for EOR and the - second low order bit on for EOF; that is, the byte will have - value 1 for EOR and value 2 for EOF. EOR and EOF may be - indicated together on the last byte transmitted by turning both - low order bits on (i.e., the value 3). If a byte of all ones - was intended to be sent as data, it should be repeated in the - second byte of the control code. - - If the structure is a file structure, the EOF is indicated by - the sending host closing the data connection and all bytes are - data bytes. - - 3.4.2. BLOCK MODE - - The file is transmitted as a series of data blocks preceded by - one or more header bytes. The header bytes contain a count - field, and descriptor code. The count field indicates the - total length of the data block in bytes, thus marking the - beginning of the next data block (there are no filler bits). - The descriptor code defines: last block in the file (EOF) last - block in the record (EOR), restart marker (see the Section on - Error Recovery and Restart) or suspect data (i.e., the data - being transferred is suspected of errors and is not reliable). - This last code is NOT intended for error control within FTP. - It is motivated by the desire of sites exchanging certain types - of data (e.g., seismic or weather data) to send and receive all - the data despite local errors (such as "magnetic tape read - errors"), but to indicate in the transmission that certain - portions are suspect). Record structures are allowed in this - mode, and any representation type may be used. - - The header consists of the three bytes. Of the 24 bits of - header information, the 16 low order bits shall represent byte - count, and the 8 high order bits shall represent descriptor - codes as shown below. - - - -Postel & Reynolds [Page 21] - - - -RFC 959 October 1985 -File Transfer Protocol - - - Block Header - - +----------------+----------------+----------------+ - | Descriptor | Byte Count | - | 8 bits | 16 bits | - +----------------+----------------+----------------+ - - - The descriptor codes are indicated by bit flags in the - descriptor byte. Four codes have been assigned, where each - code number is the decimal value of the corresponding bit in - the byte. - - Code Meaning - - 128 End of data block is EOR - 64 End of data block is EOF - 32 Suspected errors in data block - 16 Data block is a restart marker - - With this encoding, more than one descriptor coded condition - may exist for a particular block. As many bits as necessary - may be flagged. - - The restart marker is embedded in the data stream as an - integral number of 8-bit bytes representing printable - characters in the language being used over the control - connection (e.g., default--NVT-ASCII). (Space, in the - appropriate language) must not be used WITHIN a restart marker. - - For example, to transmit a six-character marker, the following - would be sent: - - +--------+--------+--------+ - |Descrptr| Byte count | - |code= 16| = 6 | - +--------+--------+--------+ - - +--------+--------+--------+ - | Marker | Marker | Marker | - | 8 bits | 8 bits | 8 bits | - +--------+--------+--------+ - - +--------+--------+--------+ - | Marker | Marker | Marker | - | 8 bits | 8 bits | 8 bits | - +--------+--------+--------+ - - -Postel & Reynolds [Page 22] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 3.4.3. COMPRESSED MODE - - There are three kinds of information to be sent: regular data, - sent in a byte string; compressed data, consisting of - replications or filler; and control information, sent in a - two-byte escape sequence. If n>0 bytes (up to 127) of regular - data are sent, these n bytes are preceded by a byte with the - left-most bit set to 0 and the right-most 7 bits containing the - number n. - - Byte string: - - 1 7 8 8 - +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ - |0| n | | d(1) | ... | d(n) | - +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ - ^ ^ - |---n bytes---| - of data - - String of n data bytes d(1),..., d(n) - Count n must be positive. - - To compress a string of n replications of the data byte d, the - following 2 bytes are sent: - - Replicated Byte: - - 2 6 8 - +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ - |1 0| n | | d | - +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ - - A string of n filler bytes can be compressed into a single - byte, where the filler byte varies with the representation - type. If the type is ASCII or EBCDIC the filler byte is - (Space, ASCII code 32, EBCDIC code 64). If the type is Image - or Local byte the filler is a zero byte. - - Filler String: - - 2 6 - +-+-+-+-+-+-+-+-+ - |1 1| n | - +-+-+-+-+-+-+-+-+ - - The escape sequence is a double byte, the first of which is the - - -Postel & Reynolds [Page 23] - - - -RFC 959 October 1985 -File Transfer Protocol - - - escape byte (all zeros) and the second of which contains - descriptor codes as defined in Block mode. The descriptor - codes have the same meaning as in Block mode and apply to the - succeeding string of bytes. - - Compressed mode is useful for obtaining increased bandwidth on - very large network transmissions at a little extra CPU cost. - It can be most effectively used to reduce the size of printer - files such as those generated by RJE hosts. - - 3.5. ERROR RECOVERY AND RESTART - - There is no provision for detecting bits lost or scrambled in data - transfer; this level of error control is handled by the TCP. - However, a restart procedure is provided to protect users from - gross system failures (including failures of a host, an - FTP-process, or the underlying network). - - The restart procedure is defined only for the block and compressed - modes of data transfer. It requires the sender of data to insert - a special marker code in the data stream with some marker - information. The marker information has meaning only to the - sender, but must consist of printable characters in the default or - negotiated language of the control connection (ASCII or EBCDIC). - The marker could represent a bit-count, a record-count, or any - other information by which a system may identify a data - checkpoint. The receiver of data, if it implements the restart - procedure, would then mark the corresponding position of this - marker in the receiving system, and return this information to the - user. - - In the event of a system failure, the user can restart the data - transfer by identifying the marker point with the FTP restart - procedure. The following example illustrates the use of the - restart procedure. - - The sender of the data inserts an appropriate marker block in the - data stream at a convenient point. The receiving host marks the - corresponding data point in its file system and conveys the last - known sender and receiver marker information to the user, either - directly or over the control connection in a 110 reply (depending - on who is the sender). In the event of a system failure, the user - or controller process restarts the server at the last server - marker by sending a restart command with server's marker code as - its argument. The restart command is transmitted over the control - - - - -Postel & Reynolds [Page 24] - - - -RFC 959 October 1985 -File Transfer Protocol - - - connection and is immediately followed by the command (such as - RETR, STOR or LIST) which was being executed when the system - failure occurred. - -4. FILE TRANSFER FUNCTIONS - - The communication channel from the user-PI to the server-PI is - established as a TCP connection from the user to the standard server - port. The user protocol interpreter is responsible for sending FTP - commands and interpreting the replies received; the server-PI - interprets commands, sends replies and directs its DTP to set up the - data connection and transfer the data. If the second party to the - data transfer (the passive transfer process) is the user-DTP, then it - is governed through the internal protocol of the user-FTP host; if it - is a second server-DTP, then it is governed by its PI on command from - the user-PI. The FTP replies are discussed in the next section. In - the description of a few of the commands in this section, it is - helpful to be explicit about the possible replies. - - 4.1. FTP COMMANDS - - 4.1.1. ACCESS CONTROL COMMANDS - - The following commands specify access control identifiers - (command codes are shown in parentheses). - - USER NAME (USER) - - The argument field is a Telnet string identifying the user. - The user identification is that which is required by the - server for access to its file system. This command will - normally be the first command transmitted by the user after - the control connections are made (some servers may require - this). Additional identification information in the form of - a password and/or an account command may also be required by - some servers. Servers may allow a new USER command to be - entered at any point in order to change the access control - and/or accounting information. This has the effect of - flushing any user, password, and account information already - supplied and beginning the login sequence again. All - transfer parameters are unchanged and any file transfer in - progress is completed under the old access control - parameters. - - - - - - -Postel & Reynolds [Page 25] - - - -RFC 959 October 1985 -File Transfer Protocol - - - PASSWORD (PASS) - - The argument field is a Telnet string specifying the user's - password. This command must be immediately preceded by the - user name command, and, for some sites, completes the user's - identification for access control. Since password - information is quite sensitive, it is desirable in general - to "mask" it or suppress typeout. It appears that the - server has no foolproof way to achieve this. It is - therefore the responsibility of the user-FTP process to hide - the sensitive password information. - - ACCOUNT (ACCT) - - The argument field is a Telnet string identifying the user's - account. The command is not necessarily related to the USER - command, as some sites may require an account for login and - others only for specific access, such as storing files. In - the latter case the command may arrive at any time. - - There are reply codes to differentiate these cases for the - automation: when account information is required for login, - the response to a successful PASSword command is reply code - 332. On the other hand, if account information is NOT - required for login, the reply to a successful PASSword - command is 230; and if the account information is needed for - a command issued later in the dialogue, the server should - return a 332 or 532 reply depending on whether it stores - (pending receipt of the ACCounT command) or discards the - command, respectively. - - CHANGE WORKING DIRECTORY (CWD) - - This command allows the user to work with a different - directory or dataset for file storage or retrieval without - altering his login or accounting information. Transfer - parameters are similarly unchanged. The argument is a - pathname specifying a directory or other system dependent - file group designator. - - CHANGE TO PARENT DIRECTORY (CDUP) - - This command is a special case of CWD, and is included to - simplify the implementation of programs for transferring - directory trees between operating systems having different - - - - -Postel & Reynolds [Page 26] - - - -RFC 959 October 1985 -File Transfer Protocol - - - syntaxes for naming the parent directory. The reply codes - shall be identical to the reply codes of CWD. See - Appendix II for further details. - - STRUCTURE MOUNT (SMNT) - - This command allows the user to mount a different file - system data structure without altering his login or - accounting information. Transfer parameters are similarly - unchanged. The argument is a pathname specifying a - directory or other system dependent file group designator. - - REINITIALIZE (REIN) - - This command terminates a USER, flushing all I/O and account - information, except to allow any transfer in progress to be - completed. All parameters are reset to the default settings - and the control connection is left open. This is identical - to the state in which a user finds himself immediately after - the control connection is opened. A USER command may be - expected to follow. - - LOGOUT (QUIT) - - This command terminates a USER and if file transfer is not - in progress, the server closes the control connection. If - file transfer is in progress, the connection will remain - open for result response and the server will then close it. - If the user-process is transferring files for several USERs - but does not wish to close and then reopen connections for - each, then the REIN command should be used instead of QUIT. - - An unexpected close on the control connection will cause the - server to take the effective action of an abort (ABOR) and a - logout (QUIT). - - 4.1.2. TRANSFER PARAMETER COMMANDS - - All data transfer parameters have default values, and the - commands specifying data transfer parameters are required only - if the default parameter values are to be changed. The default - value is the last specified value, or if no value has been - specified, the standard default value is as stated here. This - implies that the server must "remember" the applicable default - values. The commands may be in any order except that they must - precede the FTP service request. The following commands - specify data transfer parameters: - - -Postel & Reynolds [Page 27] - - - -RFC 959 October 1985 -File Transfer Protocol - - - DATA PORT (PORT) - - The argument is a HOST-PORT specification for the data port - to be used in data connection. There are defaults for both - the user and server data ports, and under normal - circumstances this command and its reply are not needed. If - this command is used, the argument is the concatenation of a - 32-bit internet host address and a 16-bit TCP port address. - This address information is broken into 8-bit fields and the - value of each field is transmitted as a decimal number (in - character string representation). The fields are separated - by commas. A port command would be: - - PORT h1,h2,h3,h4,p1,p2 - - where h1 is the high order 8 bits of the internet host - address. - - PASSIVE (PASV) - - This command requests the server-DTP to "listen" on a data - port (which is not its default data port) and to wait for a - connection rather than initiate one upon receipt of a - transfer command. The response to this command includes the - host and port address this server is listening on. - - REPRESENTATION TYPE (TYPE) - - The argument specifies the representation type as described - in the Section on Data Representation and Storage. Several - types take a second parameter. The first parameter is - denoted by a single Telnet character, as is the second - Format parameter for ASCII and EBCDIC; the second parameter - for local byte is a decimal integer to indicate Bytesize. - The parameters are separated by a (Space, ASCII code - 32). - - The following codes are assigned for type: - - \ / - A - ASCII | | N - Non-print - |-><-| T - Telnet format effectors - E - EBCDIC| | C - Carriage Control (ASA) - / \ - I - Image - - L - Local byte Byte size - - -Postel & Reynolds [Page 28] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The default representation type is ASCII Non-print. If the - Format parameter is changed, and later just the first - argument is changed, Format then returns to the Non-print - default. - - FILE STRUCTURE (STRU) - - The argument is a single Telnet character code specifying - file structure described in the Section on Data - Representation and Storage. - - The following codes are assigned for structure: - - F - File (no record structure) - R - Record structure - P - Page structure - - The default structure is File. - - TRANSFER MODE (MODE) - - The argument is a single Telnet character code specifying - the data transfer modes described in the Section on - Transmission Modes. - - The following codes are assigned for transfer modes: - - S - Stream - B - Block - C - Compressed - - The default transfer mode is Stream. - - 4.1.3. FTP SERVICE COMMANDS - - The FTP service commands define the file transfer or the file - system function requested by the user. The argument of an FTP - service command will normally be a pathname. The syntax of - pathnames must conform to server site conventions (with - standard defaults applicable), and the language conventions of - the control connection. The suggested default handling is to - use the last specified device, directory or file name, or the - standard default defined for local users. The commands may be - in any order except that a "rename from" command must be - followed by a "rename to" command and the restart command must - be followed by the interrupted service command (e.g., STOR or - RETR). The data, when transferred in response to FTP service - - -Postel & Reynolds [Page 29] - - - -RFC 959 October 1985 -File Transfer Protocol - - - commands, shall always be sent over the data connection, except - for certain informative replies. The following commands - specify FTP service requests: - - RETRIEVE (RETR) - - This command causes the server-DTP to transfer a copy of the - file, specified in the pathname, to the server- or user-DTP - at the other end of the data connection. The status and - contents of the file at the server site shall be unaffected. - - STORE (STOR) - - This command causes the server-DTP to accept the data - transferred via the data connection and to store the data as - a file at the server site. If the file specified in the - pathname exists at the server site, then its contents shall - be replaced by the data being transferred. A new file is - created at the server site if the file specified in the - pathname does not already exist. - - STORE UNIQUE (STOU) - - This command behaves like STOR except that the resultant - file is to be created in the current directory under a name - unique to that directory. The 250 Transfer Started response - must include the name generated. - - APPEND (with create) (APPE) - - This command causes the server-DTP to accept the data - transferred via the data connection and to store the data in - a file at the server site. If the file specified in the - pathname exists at the server site, then the data shall be - appended to that file; otherwise the file specified in the - pathname shall be created at the server site. - - ALLOCATE (ALLO) - - This command may be required by some servers to reserve - sufficient storage to accommodate the new file to be - transferred. The argument shall be a decimal integer - representing the number of bytes (using the logical byte - size) of storage to be reserved for the file. For files - sent with record or page structure a maximum record or page - size (in logical bytes) might also be necessary; this is - indicated by a decimal integer in a second argument field of - - -Postel & Reynolds [Page 30] - - - -RFC 959 October 1985 -File Transfer Protocol - - - the command. This second argument is optional, but when - present should be separated from the first by the three - Telnet characters R . This command shall be - followed by a STORe or APPEnd command. The ALLO command - should be treated as a NOOP (no operation) by those servers - which do not require that the maximum size of the file be - declared beforehand, and those servers interested in only - the maximum record or page size should accept a dummy value - in the first argument and ignore it. - - RESTART (REST) - - The argument field represents the server marker at which - file transfer is to be restarted. This command does not - cause file transfer but skips over the file to the specified - data checkpoint. This command shall be immediately followed - by the appropriate FTP service command which shall cause - file transfer to resume. - - RENAME FROM (RNFR) - - This command specifies the old pathname of the file which is - to be renamed. This command must be immediately followed by - a "rename to" command specifying the new file pathname. - - RENAME TO (RNTO) - - This command specifies the new pathname of the file - specified in the immediately preceding "rename from" - command. Together the two commands cause a file to be - renamed. - - ABORT (ABOR) - - This command tells the server to abort the previous FTP - service command and any associated transfer of data. The - abort command may require "special action", as discussed in - the Section on FTP Commands, to force recognition by the - server. No action is to be taken if the previous command - has been completed (including data transfer). The control - connection is not to be closed by the server, but the data - connection must be closed. - - There are two cases for the server upon receipt of this - command: (1) the FTP service command was already completed, - or (2) the FTP service command is still in progress. - - - -Postel & Reynolds [Page 31] - - - -RFC 959 October 1985 -File Transfer Protocol - - - In the first case, the server closes the data connection - (if it is open) and responds with a 226 reply, indicating - that the abort command was successfully processed. - - In the second case, the server aborts the FTP service in - progress and closes the data connection, returning a 426 - reply to indicate that the service request terminated - abnormally. The server then sends a 226 reply, - indicating that the abort command was successfully - processed. - - DELETE (DELE) - - This command causes the file specified in the pathname to be - deleted at the server site. If an extra level of protection - is desired (such as the query, "Do you really wish to - delete?"), it should be provided by the user-FTP process. - - REMOVE DIRECTORY (RMD) - - This command causes the directory specified in the pathname - to be removed as a directory (if the pathname is absolute) - or as a subdirectory of the current working directory (if - the pathname is relative). See Appendix II. - - MAKE DIRECTORY (MKD) - - This command causes the directory specified in the pathname - to be created as a directory (if the pathname is absolute) - or as a subdirectory of the current working directory (if - the pathname is relative). See Appendix II. - - PRINT WORKING DIRECTORY (PWD) - - This command causes the name of the current working - directory to be returned in the reply. See Appendix II. - - LIST (LIST) - - This command causes a list to be sent from the server to the - passive DTP. If the pathname specifies a directory or other - group of files, the server should transfer a list of files - in the specified directory. If the pathname specifies a - file then the server should send current information on the - file. A null argument implies the user's current working or - default directory. The data transfer is over the data - connection in type ASCII or type EBCDIC. (The user must - - -Postel & Reynolds [Page 32] - - - -RFC 959 October 1985 -File Transfer Protocol - - - ensure that the TYPE is appropriately ASCII or EBCDIC). - Since the information on a file may vary widely from system - to system, this information may be hard to use automatically - in a program, but may be quite useful to a human user. - - NAME LIST (NLST) - - This command causes a directory listing to be sent from - server to user site. The pathname should specify a - directory or other system-specific file group descriptor; a - null argument implies the current directory. The server - will return a stream of names of files and no other - information. The data will be transferred in ASCII or - EBCDIC type over the data connection as valid pathname - strings separated by or . (Again the user must - ensure that the TYPE is correct.) This command is intended - to return information that can be used by a program to - further process the files automatically. For example, in - the implementation of a "multiple get" function. - - SITE PARAMETERS (SITE) - - This command is used by the server to provide services - specific to his system that are essential to file transfer - but not sufficiently universal to be included as commands in - the protocol. The nature of these services and the - specification of their syntax can be stated in a reply to - the HELP SITE command. - - SYSTEM (SYST) - - This command is used to find out the type of operating - system at the server. The reply shall have as its first - word one of the system names listed in the current version - of the Assigned Numbers document [4]. - - STATUS (STAT) - - This command shall cause a status response to be sent over - the control connection in the form of a reply. The command - may be sent during a file transfer (along with the Telnet IP - and Synch signals--see the Section on FTP Commands) in which - case the server will respond with the status of the - operation in progress, or it may be sent between file - transfers. In the latter case, the command may have an - argument field. If the argument is a pathname, the command - is analogous to the "list" command except that data shall be - - -Postel & Reynolds [Page 33] - - - -RFC 959 October 1985 -File Transfer Protocol - - - transferred over the control connection. If a partial - pathname is given, the server may respond with a list of - file names or attributes associated with that specification. - If no argument is given, the server should return general - status information about the server FTP process. This - should include current values of all transfer parameters and - the status of connections. - - HELP (HELP) - - This command shall cause the server to send helpful - information regarding its implementation status over the - control connection to the user. The command may take an - argument (e.g., any command name) and return more specific - information as a response. The reply is type 211 or 214. - It is suggested that HELP be allowed before entering a USER - command. The server may use this reply to specify - site-dependent parameters, e.g., in response to HELP SITE. - - NOOP (NOOP) - - This command does not affect any parameters or previously - entered commands. It specifies no action other than that the - server send an OK reply. - - The File Transfer Protocol follows the specifications of the Telnet - protocol for all communications over the control connection. Since - the language used for Telnet communication may be a negotiated - option, all references in the next two sections will be to the - "Telnet language" and the corresponding "Telnet end-of-line code". - Currently, one may take these to mean NVT-ASCII and . No other - specifications of the Telnet protocol will be cited. - - FTP commands are "Telnet strings" terminated by the "Telnet end of - line code". The command codes themselves are alphabetic characters - terminated by the character (Space) if parameters follow and - Telnet-EOL otherwise. The command codes and the semantics of - commands are described in this section; the detailed syntax of - commands is specified in the Section on Commands, the reply sequences - are discussed in the Section on Sequencing of Commands and Replies, - and scenarios illustrating the use of commands are provided in the - Section on Typical FTP Scenarios. - - FTP commands may be partitioned as those specifying access-control - identifiers, data transfer parameters, or FTP service requests. - Certain commands (such as ABOR, STAT, QUIT) may be sent over the - control connection while a data transfer is in progress. Some - - -Postel & Reynolds [Page 34] - - - -RFC 959 October 1985 -File Transfer Protocol - - - servers may not be able to monitor the control and data connections - simultaneously, in which case some special action will be necessary - to get the server's attention. The following ordered format is - tentatively recommended: - - 1. User system inserts the Telnet "Interrupt Process" (IP) signal - in the Telnet stream. - - 2. User system sends the Telnet "Synch" signal. - - 3. User system inserts the command (e.g., ABOR) in the Telnet - stream. - - 4. Server PI, after receiving "IP", scans the Telnet stream for - EXACTLY ONE FTP command. - - (For other servers this may not be necessary but the actions listed - above should have no unusual effect.) - - 4.2. FTP REPLIES - - Replies to File Transfer Protocol commands are devised to ensure - the synchronization of requests and actions in the process of file - transfer, and to guarantee that the user process always knows the - state of the Server. Every command must generate at least one - reply, although there may be more than one; in the latter case, - the multiple replies must be easily distinguished. In addition, - some commands occur in sequential groups, such as USER, PASS and - ACCT, or RNFR and RNTO. The replies show the existence of an - intermediate state if all preceding commands have been successful. - A failure at any point in the sequence necessitates the repetition - of the entire sequence from the beginning. - - The details of the command-reply sequence are made explicit in - a set of state diagrams below. - - An FTP reply consists of a three digit number (transmitted as - three alphanumeric characters) followed by some text. The number - is intended for use by automata to determine what state to enter - next; the text is intended for the human user. It is intended - that the three digits contain enough encoded information that the - user-process (the User-PI) will not need to examine the text and - may either discard it or pass it on to the user, as appropriate. - In particular, the text may be server-dependent, so there are - likely to be varying texts for each reply code. - - A reply is defined to contain the 3-digit code, followed by Space - - -Postel & Reynolds [Page 35] - - - -RFC 959 October 1985 -File Transfer Protocol - - - , followed by one line of text (where some maximum line length - has been specified), and terminated by the Telnet end-of-line - code. There will be cases however, where the text is longer than - a single line. In these cases the complete text must be bracketed - so the User-process knows when it may stop reading the reply (i.e. - stop processing input on the control connection) and go do other - things. This requires a special format on the first line to - indicate that more than one line is coming, and another on the - last line to designate it as the last. At least one of these must - contain the appropriate reply code to indicate the state of the - transaction. To satisfy all factions, it was decided that both - the first and last line codes should be the same. - - Thus the format for multi-line replies is that the first line - will begin with the exact required reply code, followed - immediately by a Hyphen, "-" (also known as Minus), followed by - text. The last line will begin with the same code, followed - immediately by Space , optionally some text, and the Telnet - end-of-line code. - - For example: - 123-First line - Second line - 234 A line beginning with numbers - 123 The last line - - The user-process then simply needs to search for the second - occurrence of the same reply code, followed by (Space), at - the beginning of a line, and ignore all intermediary lines. If - an intermediary line begins with a 3-digit number, the Server - must pad the front to avoid confusion. - - This scheme allows standard system routines to be used for - reply information (such as for the STAT reply), with - "artificial" first and last lines tacked on. In rare cases - where these routines are able to generate three digits and a - Space at the beginning of any line, the beginning of each - text line should be offset by some neutral text, like Space. - - This scheme assumes that multi-line replies may not be nested. - - The three digits of the reply each have a special significance. - This is intended to allow a range of very simple to very - sophisticated responses by the user-process. The first digit - denotes whether the response is good, bad or incomplete. - (Referring to the state diagram), an unsophisticated user-process - will be able to determine its next action (proceed as planned, - - -Postel & Reynolds [Page 36] - - - -RFC 959 October 1985 -File Transfer Protocol - - - redo, retrench, etc.) by simply examining this first digit. A - user-process that wants to know approximately what kind of error - occurred (e.g. file system error, command syntax error) may - examine the second digit, reserving the third digit for the finest - gradation of information (e.g., RNTO command without a preceding - RNFR). - - There are five values for the first digit of the reply code: - - 1yz Positive Preliminary reply - - The requested action is being initiated; expect another - reply before proceeding with a new command. (The - user-process sending another command before the - completion reply would be in violation of protocol; but - server-FTP processes should queue any commands that - arrive while a preceding command is in progress.) This - type of reply can be used to indicate that the command - was accepted and the user-process may now pay attention - to the data connections, for implementations where - simultaneous monitoring is difficult. The server-FTP - process may send at most, one 1yz reply per command. - - 2yz Positive Completion reply - - The requested action has been successfully completed. A - new request may be initiated. - - 3yz Positive Intermediate reply - - The command has been accepted, but the requested action - is being held in abeyance, pending receipt of further - information. The user should send another command - specifying this information. This reply is used in - command sequence groups. - - 4yz Transient Negative Completion reply - - The command was not accepted and the requested action did - not take place, but the error condition is temporary and - the action may be requested again. The user should - return to the beginning of the command sequence, if any. - It is difficult to assign a meaning to "transient", - particularly when two distinct sites (Server- and - User-processes) have to agree on the interpretation. - Each reply in the 4yz category might have a slightly - different time value, but the intent is that the - - -Postel & Reynolds [Page 37] - - - -RFC 959 October 1985 -File Transfer Protocol - - - user-process is encouraged to try again. A rule of thumb - in determining if a reply fits into the 4yz or the 5yz - (Permanent Negative) category is that replies are 4yz if - the commands can be repeated without any change in - command form or in properties of the User or Server - (e.g., the command is spelled the same with the same - arguments used; the user does not change his file access - or user name; the server does not put up a new - implementation.) - - 5yz Permanent Negative Completion reply - - The command was not accepted and the requested action did - not take place. The User-process is discouraged from - repeating the exact request (in the same sequence). Even - some "permanent" error conditions can be corrected, so - the human user may want to direct his User-process to - reinitiate the command sequence by direct action at some - point in the future (e.g., after the spelling has been - changed, or the user has altered his directory status.) - - The following function groupings are encoded in the second - digit: - - x0z Syntax - These replies refer to syntax errors, - syntactically correct commands that don't fit any - functional category, unimplemented or superfluous - commands. - - x1z Information - These are replies to requests for - information, such as status or help. - - x2z Connections - Replies referring to the control and - data connections. - - x3z Authentication and accounting - Replies for the login - process and accounting procedures. - - x4z Unspecified as yet. - - x5z File system - These replies indicate the status of the - Server file system vis-a-vis the requested transfer or - other file system action. - - The third digit gives a finer gradation of meaning in each of - the function categories, specified by the second digit. The - list of replies below will illustrate this. Note that the text - - -Postel & Reynolds [Page 38] - - - -RFC 959 October 1985 -File Transfer Protocol - - - associated with each reply is recommended, rather than - mandatory, and may even change according to the command with - which it is associated. The reply codes, on the other hand, - must strictly follow the specifications in the last section; - that is, Server implementations should not invent new codes for - situations that are only slightly different from the ones - described here, but rather should adapt codes already defined. - - A command such as TYPE or ALLO whose successful execution - does not offer the user-process any new information will - cause a 200 reply to be returned. If the command is not - implemented by a particular Server-FTP process because it - has no relevance to that computer system, for example ALLO - at a TOPS20 site, a Positive Completion reply is still - desired so that the simple User-process knows it can proceed - with its course of action. A 202 reply is used in this case - with, for example, the reply text: "No storage allocation - necessary." If, on the other hand, the command requests a - non-site-specific action and is unimplemented, the response - is 502. A refinement of that is the 504 reply for a command - that is implemented, but that requests an unimplemented - parameter. - - 4.2.1 Reply Codes by Function Groups - - 200 Command okay. - 500 Syntax error, command unrecognized. - This may include errors such as command line too long. - 501 Syntax error in parameters or arguments. - 202 Command not implemented, superfluous at this site. - 502 Command not implemented. - 503 Bad sequence of commands. - 504 Command not implemented for that parameter. - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 39] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 110 Restart marker reply. - In this case, the text is exact and not left to the - particular implementation; it must read: - MARK yyyy = mmmm - Where yyyy is User-process data stream marker, and mmmm - server's equivalent marker (note the spaces between markers - and "="). - 211 System status, or system help reply. - 212 Directory status. - 213 File status. - 214 Help message. - On how to use the server or the meaning of a particular - non-standard command. This reply is useful only to the - human user. - 215 NAME system type. - Where NAME is an official system name from the list in the - Assigned Numbers document. - - 120 Service ready in nnn minutes. - 220 Service ready for new user. - 221 Service closing control connection. - Logged out if appropriate. - 421 Service not available, closing control connection. - This may be a reply to any command if the service knows it - must shut down. - 125 Data connection already open; transfer starting. - 225 Data connection open; no transfer in progress. - 425 Can't open data connection. - 226 Closing data connection. - Requested file action successful (for example, file - transfer or file abort). - 426 Connection closed; transfer aborted. - 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). - - 230 User logged in, proceed. - 530 Not logged in. - 331 User name okay, need password. - 332 Need account for login. - 532 Need account for storing files. - - - - - - - - - - -Postel & Reynolds [Page 40] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 150 File status okay; about to open data connection. - 250 Requested file action okay, completed. - 257 "PATHNAME" created. - 350 Requested file action pending further information. - 450 Requested file action not taken. - File unavailable (e.g., file busy). - 550 Requested action not taken. - File unavailable (e.g., file not found, no access). - 451 Requested action aborted. Local error in processing. - 551 Requested action aborted. Page type unknown. - 452 Requested action not taken. - Insufficient storage space in system. - 552 Requested file action aborted. - Exceeded storage allocation (for current directory or - dataset). - 553 Requested action not taken. - File name not allowed. - - - 4.2.2 Numeric Order List of Reply Codes - - 110 Restart marker reply. - In this case, the text is exact and not left to the - particular implementation; it must read: - MARK yyyy = mmmm - Where yyyy is User-process data stream marker, and mmmm - server's equivalent marker (note the spaces between markers - and "="). - 120 Service ready in nnn minutes. - 125 Data connection already open; transfer starting. - 150 File status okay; about to open data connection. - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 41] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 200 Command okay. - 202 Command not implemented, superfluous at this site. - 211 System status, or system help reply. - 212 Directory status. - 213 File status. - 214 Help message. - On how to use the server or the meaning of a particular - non-standard command. This reply is useful only to the - human user. - 215 NAME system type. - Where NAME is an official system name from the list in the - Assigned Numbers document. - 220 Service ready for new user. - 221 Service closing control connection. - Logged out if appropriate. - 225 Data connection open; no transfer in progress. - 226 Closing data connection. - Requested file action successful (for example, file - transfer or file abort). - 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). - 230 User logged in, proceed. - 250 Requested file action okay, completed. - 257 "PATHNAME" created. - - 331 User name okay, need password. - 332 Need account for login. - 350 Requested file action pending further information. - - 421 Service not available, closing control connection. - This may be a reply to any command if the service knows it - must shut down. - 425 Can't open data connection. - 426 Connection closed; transfer aborted. - 450 Requested file action not taken. - File unavailable (e.g., file busy). - 451 Requested action aborted: local error in processing. - 452 Requested action not taken. - Insufficient storage space in system. - - - - - - - - - - - -Postel & Reynolds [Page 42] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 500 Syntax error, command unrecognized. - This may include errors such as command line too long. - 501 Syntax error in parameters or arguments. - 502 Command not implemented. - 503 Bad sequence of commands. - 504 Command not implemented for that parameter. - 530 Not logged in. - 532 Need account for storing files. - 550 Requested action not taken. - File unavailable (e.g., file not found, no access). - 551 Requested action aborted: page type unknown. - 552 Requested file action aborted. - Exceeded storage allocation (for current directory or - dataset). - 553 Requested action not taken. - File name not allowed. - - -5. DECLARATIVE SPECIFICATIONS - - 5.1. MINIMUM IMPLEMENTATION - - In order to make FTP workable without needless error messages, the - following minimum implementation is required for all servers: - - TYPE - ASCII Non-print - MODE - Stream - STRUCTURE - File, Record - COMMANDS - USER, QUIT, PORT, - TYPE, MODE, STRU, - for the default values - RETR, STOR, - NOOP. - - The default values for transfer parameters are: - - TYPE - ASCII Non-print - MODE - Stream - STRU - File - - All hosts must accept the above as the standard defaults. - - - - - - - - -Postel & Reynolds [Page 43] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 5.2. CONNECTIONS - - The server protocol interpreter shall "listen" on Port L. The - user or user protocol interpreter shall initiate the full-duplex - control connection. Server- and user- processes should follow the - conventions of the Telnet protocol as specified in the - ARPA-Internet Protocol Handbook [1]. Servers are under no - obligation to provide for editing of command lines and may require - that it be done in the user host. The control connection shall be - closed by the server at the user's request after all transfers and - replies are completed. - - The user-DTP must "listen" on the specified data port; this may be - the default user port (U) or a port specified in the PORT command. - The server shall initiate the data connection from his own default - data port (L-1) using the specified user data port. The direction - of the transfer and the port used will be determined by the FTP - service command. - - Note that all FTP implementation must support data transfer using - the default port, and that only the USER-PI may initiate the use - of non-default ports. - - When data is to be transferred between two servers, A and B (refer - to Figure 2), the user-PI, C, sets up control connections with - both server-PI's. One of the servers, say A, is then sent a PASV - command telling him to "listen" on his data port rather than - initiate a connection when he receives a transfer service command. - When the user-PI receives an acknowledgment to the PASV command, - which includes the identity of the host and port being listened - on, the user-PI then sends A's port, a, to B in a PORT command; a - reply is returned. The user-PI may then send the corresponding - service commands to A and B. Server B initiates the connection - and the transfer proceeds. The command-reply sequence is listed - below where the messages are vertically synchronous but - horizontally asynchronous: - - - - - - - - - - - - - -Postel & Reynolds [Page 44] - - - -RFC 959 October 1985 -File Transfer Protocol - - - User-PI - Server A User-PI - Server B - ------------------ ------------------ - - C->A : Connect C->B : Connect - C->A : PASV - A->C : 227 Entering Passive Mode. A1,A2,A3,A4,a1,a2 - C->B : PORT A1,A2,A3,A4,a1,a2 - B->C : 200 Okay - C->A : STOR C->B : RETR - B->A : Connect to HOST-A, PORT-a - - Figure 3 - - The data connection shall be closed by the server under the - conditions described in the Section on Establishing Data - Connections. If the data connection is to be closed following a - data transfer where closing the connection is not required to - indicate the end-of-file, the server must do so immediately. - Waiting until after a new transfer command is not permitted - because the user-process will have already tested the data - connection to see if it needs to do a "listen"; (remember that the - user must "listen" on a closed data port BEFORE sending the - transfer request). To prevent a race condition here, the server - sends a reply (226) after closing the data connection (or if the - connection is left open, a "file transfer completed" reply (250) - and the user-PI should wait for one of these replies before - issuing a new transfer command). - - Any time either the user or server see that the connection is - being closed by the other side, it should promptly read any - remaining data queued on the connection and issue the close on its - own side. - - 5.3. COMMANDS - - The commands are Telnet character strings transmitted over the - control connections as described in the Section on FTP Commands. - The command functions and semantics are described in the Section - on Access Control Commands, Transfer Parameter Commands, FTP - Service Commands, and Miscellaneous Commands. The command syntax - is specified here. - - The commands begin with a command code followed by an argument - field. The command codes are four or fewer alphabetic characters. - Upper and lower case alphabetic characters are to be treated - identically. Thus, any of the following may represent the - retrieve command: - - -Postel & Reynolds [Page 45] - - - -RFC 959 October 1985 -File Transfer Protocol - - - RETR Retr retr ReTr rETr - - This also applies to any symbols representing parameter values, - such as A or a for ASCII TYPE. The command codes and the argument - fields are separated by one or more spaces. - - The argument field consists of a variable length character string - ending with the character sequence (Carriage Return, Line - Feed) for NVT-ASCII representation; for other negotiated languages - a different end of line character might be used. It should be - noted that the server is to take no action until the end of line - code is received. - - The syntax is specified below in NVT-ASCII. All characters in the - argument field are ASCII characters including any ASCII - represented decimal integers. Square brackets denote an optional - argument field. If the option is not taken, the appropriate - default is implied. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 46] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 5.3.1. FTP COMMANDS - - The following are the FTP commands: - - USER - PASS - ACCT - CWD - CDUP - SMNT - QUIT - REIN - PORT - PASV - TYPE - STRU - MODE - RETR - STOR - STOU - APPE - ALLO - [ R ] - REST - RNFR - RNTO - ABOR - DELE - RMD - MKD - PWD - LIST [ ] - NLST [ ] - SITE - SYST - STAT [ ] - HELP [ ] - NOOP - - - - - - - - - - - -Postel & Reynolds [Page 47] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 5.3.2. FTP COMMAND ARGUMENTS - - The syntax of the above argument fields (using BNF notation - where applicable) is: - - ::= - ::= - ::= - ::= | - ::= any of the 128 ASCII characters except and - - ::= - ::= | - ::= printable characters, any - ASCII code 33 through 126 - ::= - ::= , - ::= ,,, - ::= , - ::= any decimal integer 1 through 255 - ::= N | T | C - ::= A [ ] - | E [ ] - | I - | L - ::= F | R | P - ::= S | B | C - ::= - ::= any decimal integer - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 48] - - - -RFC 959 October 1985 -File Transfer Protocol - - - 5.4. SEQUENCING OF COMMANDS AND REPLIES - - The communication between the user and server is intended to be an - alternating dialogue. As such, the user issues an FTP command and - the server responds with a prompt primary reply. The user should - wait for this initial primary success or failure response before - sending further commands. - - Certain commands require a second reply for which the user should - also wait. These replies may, for example, report on the progress - or completion of file transfer or the closing of the data - connection. They are secondary replies to file transfer commands. - - One important group of informational replies is the connection - greetings. Under normal circumstances, a server will send a 220 - reply, "awaiting input", when the connection is completed. The - user should wait for this greeting message before sending any - commands. If the server is unable to accept input right away, a - 120 "expected delay" reply should be sent immediately and a 220 - reply when ready. The user will then know not to hang up if there - is a delay. - - Spontaneous Replies - - Sometimes "the system" spontaneously has a message to be sent - to a user (usually all users). For example, "System going down - in 15 minutes". There is no provision in FTP for such - spontaneous information to be sent from the server to the user. - It is recommended that such information be queued in the - server-PI and delivered to the user-PI in the next reply - (possibly making it a multi-line reply). - - The table below lists alternative success and failure replies for - each command. These must be strictly adhered to; a server may - substitute text in the replies, but the meaning and action implied - by the code numbers and by the specific command reply sequence - cannot be altered. - - Command-Reply Sequences - - In this section, the command-reply sequence is presented. Each - command is listed with its possible replies; command groups are - listed together. Preliminary replies are listed first (with - their succeeding replies indented and under them), then - positive and negative completion, and finally intermediary - - - - -Postel & Reynolds [Page 49] - - - -RFC 959 October 1985 -File Transfer Protocol - - - replies with the remaining commands from the sequence - following. This listing forms the basis for the state - diagrams, which will be presented separately. - - Connection Establishment - 120 - 220 - 220 - 421 - Login - USER - 230 - 530 - 500, 501, 421 - 331, 332 - PASS - 230 - 202 - 530 - 500, 501, 503, 421 - 332 - ACCT - 230 - 202 - 530 - 500, 501, 503, 421 - CWD - 250 - 500, 501, 502, 421, 530, 550 - CDUP - 200 - 500, 501, 502, 421, 530, 550 - SMNT - 202, 250 - 500, 501, 502, 421, 530, 550 - Logout - REIN - 120 - 220 - 220 - 421 - 500, 502 - QUIT - 221 - 500 - - - - -Postel & Reynolds [Page 50] - - - -RFC 959 October 1985 -File Transfer Protocol - - - Transfer parameters - PORT - 200 - 500, 501, 421, 530 - PASV - 227 - 500, 501, 502, 421, 530 - MODE - 200 - 500, 501, 504, 421, 530 - TYPE - 200 - 500, 501, 504, 421, 530 - STRU - 200 - 500, 501, 504, 421, 530 - File action commands - ALLO - 200 - 202 - 500, 501, 504, 421, 530 - REST - 500, 501, 502, 421, 530 - 350 - STOR - 125, 150 - (110) - 226, 250 - 425, 426, 451, 551, 552 - 532, 450, 452, 553 - 500, 501, 421, 530 - STOU - 125, 150 - (110) - 226, 250 - 425, 426, 451, 551, 552 - 532, 450, 452, 553 - 500, 501, 421, 530 - RETR - 125, 150 - (110) - 226, 250 - 425, 426, 451 - 450, 550 - 500, 501, 421, 530 - - - - -Postel & Reynolds [Page 51] - - - -RFC 959 October 1985 -File Transfer Protocol - - - LIST - 125, 150 - 226, 250 - 425, 426, 451 - 450 - 500, 501, 502, 421, 530 - NLST - 125, 150 - 226, 250 - 425, 426, 451 - 450 - 500, 501, 502, 421, 530 - APPE - 125, 150 - (110) - 226, 250 - 425, 426, 451, 551, 552 - 532, 450, 550, 452, 553 - 500, 501, 502, 421, 530 - RNFR - 450, 550 - 500, 501, 502, 421, 530 - 350 - RNTO - 250 - 532, 553 - 500, 501, 502, 503, 421, 530 - DELE - 250 - 450, 550 - 500, 501, 502, 421, 530 - RMD - 250 - 500, 501, 502, 421, 530, 550 - MKD - 257 - 500, 501, 502, 421, 530, 550 - PWD - 257 - 500, 501, 502, 421, 550 - ABOR - 225, 226 - 500, 501, 502, 421 - - - - - - -Postel & Reynolds [Page 52] - - - -RFC 959 October 1985 -File Transfer Protocol - - - Informational commands - SYST - 215 - 500, 501, 502, 421 - STAT - 211, 212, 213 - 450 - 500, 501, 502, 421, 530 - HELP - 211, 214 - 500, 501, 502, 421 - Miscellaneous commands - SITE - 200 - 202 - 500, 501, 530 - NOOP - 200 - 500 421 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 53] - - - -RFC 959 October 1985 -File Transfer Protocol - - -6. STATE DIAGRAMS - - Here we present state diagrams for a very simple minded FTP - implementation. Only the first digit of the reply codes is used. - There is one state diagram for each group of FTP commands or command - sequences. - - The command groupings were determined by constructing a model for - each command then collecting together the commands with structurally - identical models. - - For each command or command sequence there are three possible - outcomes: success (S), failure (F), and error (E). In the state - diagrams below we use the symbol B for "begin", and the symbol W for - "wait for reply". - - We first present the diagram that represents the largest group of FTP - commands: - - - 1,3 +---+ - ----------->| E | - | +---+ - | - +---+ cmd +---+ 2 +---+ - | B |---------->| W |---------->| S | - +---+ +---+ +---+ - | - | 4,5 +---+ - ----------->| F | - +---+ - - - This diagram models the commands: - - ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV, - QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, and TYPE. - - - - - - - - - - - - -Postel & Reynolds [Page 54] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The other large group of commands is represented by a very similar - diagram: - - - 3 +---+ - ----------->| E | - | +---+ - | - +---+ cmd +---+ 2 +---+ - | B |---------->| W |---------->| S | - +---+ --->+---+ +---+ - | | | - | | | 4,5 +---+ - | 1 | ----------->| F | - ----- +---+ - - - This diagram models the commands: - - APPE, LIST, NLST, REIN, RETR, STOR, and STOU. - - Note that this second model could also be used to represent the first - group of commands, the only difference being that in the first group - the 100 series replies are unexpected and therefore treated as error, - while the second group expects (some may require) 100 series replies. - Remember that at most, one 100 series reply is allowed per command. - - The remaining diagrams model command sequences, perhaps the simplest - of these is the rename sequence: - - - +---+ RNFR +---+ 1,2 +---+ - | B |---------->| W |---------->| E | - +---+ +---+ -->+---+ - | | | - 3 | | 4,5 | - -------------- ------ | - | | | +---+ - | ------------->| S | - | | 1,3 | | +---+ - | 2| -------- - | | | | - V | | | - +---+ RNTO +---+ 4,5 ----->+---+ - | |---------->| W |---------->| F | - +---+ +---+ +---+ - - - -Postel & Reynolds [Page 55] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The next diagram is a simple model of the Restart command: - - - +---+ REST +---+ 1,2 +---+ - | B |---------->| W |---------->| E | - +---+ +---+ -->+---+ - | | | - 3 | | 4,5 | - -------------- ------ | - | | | +---+ - | ------------->| S | - | | 3 | | +---+ - | 2| -------- - | | | | - V | | | - +---+ cmd +---+ 4,5 ----->+---+ - | |---------->| W |---------->| F | - +---+ -->+---+ +---+ - | | - | 1 | - ------ - - - Where "cmd" is APPE, STOR, or RETR. - - We note that the above three models are similar. The Restart differs - from the Rename two only in the treatment of 100 series replies at - the second stage, while the second group expects (some may require) - 100 series replies. Remember that at most, one 100 series reply is - allowed per command. - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 56] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The most complicated diagram is for the Login sequence: - - - 1 - +---+ USER +---+------------->+---+ - | B |---------->| W | 2 ---->| E | - +---+ +---+------ | -->+---+ - | | | | | - 3 | | 4,5 | | | - -------------- ----- | | | - | | | | | - | | | | | - | --------- | - | 1| | | | - V | | | | - +---+ PASS +---+ 2 | ------>+---+ - | |---------->| W |------------->| S | - +---+ +---+ ---------->+---+ - | | | | | - 3 | |4,5| | | - -------------- -------- | - | | | | | - | | | | | - | ----------- - | 1,3| | | | - V | 2| | | - +---+ ACCT +---+-- | ----->+---+ - | |---------->| W | 4,5 -------->| F | - +---+ +---+------------->+---+ - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 57] - - - -RFC 959 October 1985 -File Transfer Protocol - - - Finally, we present a generalized diagram that could be used to model - the command and reply interchange: - - - ------------------------------------ - | | - Begin | | - | V | - | +---+ cmd +---+ 2 +---+ | - -->| |------->| |---------->| | | - | | | W | | S |-----| - -->| | -->| |----- | | | - | +---+ | +---+ 4,5 | +---+ | - | | | | | | | - | | | 1| |3 | +---+ | - | | | | | | | | | - | | ---- | ---->| F |----- - | | | | | - | | | +---+ - ------------------- - | - | - V - End - - - - - - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 58] - - - -RFC 959 October 1985 -File Transfer Protocol - - -7. TYPICAL FTP SCENARIO - - User at host U wanting to transfer files to/from host S: - - In general, the user will communicate to the server via a mediating - user-FTP process. The following may be a typical scenario. The - user-FTP prompts are shown in parentheses, '---->' represents - commands from host U to host S, and '<----' represents replies from - host S to host U. - - LOCAL COMMANDS BY USER ACTION INVOLVED - - ftp (host) multics Connect to host S, port L, - establishing control connections. - <---- 220 Service ready . - username Doe USER Doe----> - <---- 331 User name ok, - need password. - password mumble PASS mumble----> - <---- 230 User logged in. - retrieve (local type) ASCII - (local pathname) test 1 User-FTP opens local file in ASCII. - (for. pathname) test.pl1 RETR test.pl1 ----> - <---- 150 File status okay; - about to open data - connection. - Server makes data connection - to port U. - - <---- 226 Closing data connection, - file transfer successful. - type Image TYPE I ----> - <---- 200 Command OK - store (local type) image - (local pathname) file dump User-FTP opens local file in Image. - (for.pathname) >udd>cn>fd STOR >udd>cn>fd ----> - <---- 550 Access denied - terminate QUIT ----> - Server closes all - connections. - -8. CONNECTION ESTABLISHMENT - - The FTP control connection is established via TCP between the user - process port U and the server process port L. This protocol is - assigned the service port 21 (25 octal), that is L=21. - - - -Postel & Reynolds [Page 59] - - - -RFC 959 October 1985 -File Transfer Protocol - - -APPENDIX I - PAGE STRUCTURE - - The need for FTP to support page structure derives principally from - the need to support efficient transmission of files between TOPS-20 - systems, particularly the files used by NLS. - - The file system of TOPS-20 is based on the concept of pages. The - operating system is most efficient at manipulating files as pages. - The operating system provides an interface to the file system so that - many applications view files as sequential streams of characters. - However, a few applications use the underlying page structures - directly, and some of these create holey files. - - A TOPS-20 disk file consists of four things: a pathname, a page - table, a (possibly empty) set of pages, and a set of attributes. - - The pathname is specified in the RETR or STOR command. It includes - the directory name, file name, file name extension, and generation - number. - - The page table contains up to 2**18 entries. Each entry may be - EMPTY, or may point to a page. If it is not empty, there are also - some page-specific access bits; not all pages of a file need have the - same access protection. - - A page is a contiguous set of 512 words of 36 bits each. - - The attributes of the file, in the File Descriptor Block (FDB), - contain such things as creation time, write time, read time, writer's - byte-size, end-of-file pointer, count of reads and writes, backup - system tape numbers, etc. - - Note that there is NO requirement that entries in the page table be - contiguous. There may be empty page table slots between occupied - ones. Also, the end of file pointer is simply a number. There is no - requirement that it in fact point at the "last" datum in the file. - Ordinary sequential I/O calls in TOPS-20 will cause the end of file - pointer to be left after the last datum written, but other operations - may cause it not to be so, if a particular programming system so - requires. - - In fact, in both of these special cases, "holey" files and - end-of-file pointers NOT at the end of the file, occur with NLS data - files. - - - - - -Postel & Reynolds [Page 60] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The TOPS-20 paged files can be sent with the FTP transfer parameters: - TYPE L 36, STRU P, and MODE S (in fact, any mode could be used). - - Each page of information has a header. Each header field, which is a - logical byte, is a TOPS-20 word, since the TYPE is L 36. - - The header fields are: - - Word 0: Header Length. - - The header length is 5. - - Word 1: Page Index. - - If the data is a disk file page, this is the number of that - page in the file's page map. Empty pages (holes) in the file - are simply not sent. Note that a hole is NOT the same as a - page of zeros. - - Word 2: Data Length. - - The number of data words in this page, following the header. - Thus, the total length of the transmission unit is the Header - Length plus the Data Length. - - Word 3: Page Type. - - A code for what type of chunk this is. A data page is type 3, - the FDB page is type 2. - - Word 4: Page Access Control. - - The access bits associated with the page in the file's page - map. (This full word quantity is put into AC2 of an SPACS by - the program reading from net to disk.) - - After the header are Data Length data words. Data Length is - currently either 512 for a data page or 31 for an FDB. Trailing - zeros in a disk file page may be discarded, making Data Length less - than 512 in that case. - - - - - - - - - -Postel & Reynolds [Page 61] - - - -RFC 959 October 1985 -File Transfer Protocol - - -APPENDIX II - DIRECTORY COMMANDS - - Since UNIX has a tree-like directory structure in which directories - are as easy to manipulate as ordinary files, it is useful to expand - the FTP servers on these machines to include commands which deal with - the creation of directories. Since there are other hosts on the - ARPA-Internet which have tree-like directories (including TOPS-20 and - Multics), these commands are as general as possible. - - Four directory commands have been added to FTP: - - MKD pathname - - Make a directory with the name "pathname". - - RMD pathname - - Remove the directory with the name "pathname". - - PWD - - Print the current working directory name. - - CDUP - - Change to the parent of the current working directory. - - The "pathname" argument should be created (removed) as a - subdirectory of the current working directory, unless the "pathname" - string contains sufficient information to specify otherwise to the - server, e.g., "pathname" is an absolute pathname (in UNIX and - Multics), or pathname is something like "" to - TOPS-20. - - REPLY CODES - - The CDUP command is a special case of CWD, and is included to - simplify the implementation of programs for transferring directory - trees between operating systems having different syntaxes for - naming the parent directory. The reply codes for CDUP be - identical to the reply codes of CWD. - - The reply codes for RMD be identical to the reply codes for its - file analogue, DELE. - - The reply codes for MKD, however, are a bit more complicated. A - freshly created directory will probably be the object of a future - - -Postel & Reynolds [Page 62] - - - -RFC 959 October 1985 -File Transfer Protocol - - - CWD command. Unfortunately, the argument to MKD may not always be - a suitable argument for CWD. This is the case, for example, when - a TOPS-20 subdirectory is created by giving just the subdirectory - name. That is, with a TOPS-20 server FTP, the command sequence - - MKD MYDIR - CWD MYDIR - - will fail. The new directory may only be referred to by its - "absolute" name; e.g., if the MKD command above were issued while - connected to the directory , the new subdirectory - could only be referred to by the name . - - Even on UNIX and Multics, however, the argument given to MKD may - not be suitable. If it is a "relative" pathname (i.e., a pathname - which is interpreted relative to the current directory), the user - would need to be in the same current directory in order to reach - the subdirectory. Depending on the application, this may be - inconvenient. It is not very robust in any case. - - To solve these problems, upon successful completion of an MKD - command, the server should return a line of the form: - - 257"" - - That is, the server will tell the user what string to use when - referring to the created directory. The directory name can - contain any character; embedded double-quotes should be escaped by - double-quotes (the "quote-doubling" convention). - - For example, a user connects to the directory /usr/dm, and creates - a subdirectory, named pathname: - - CWD /usr/dm - 200 directory changed to /usr/dm - MKD pathname - 257 "/usr/dm/pathname" directory created - - An example with an embedded double quote: - - MKD foo"bar - 257 "/usr/dm/foo""bar" directory created - CWD /usr/dm/foo"bar - 200 directory changed to /usr/dm/foo"bar - - - - - -Postel & Reynolds [Page 63] - - - -RFC 959 October 1985 -File Transfer Protocol - - - The prior existence of a subdirectory with the same name is an - error, and the server must return an "access denied" error reply - in that case. - - CWD /usr/dm - 200 directory changed to /usr/dm - MKD pathname - 521-"/usr/dm/pathname" directory already exists; - 521 taking no action. - - The failure replies for MKD are analogous to its file creating - cousin, STOR. Also, an "access denied" return is given if a file - name with the same name as the subdirectory will conflict with the - creation of the subdirectory (this is a problem on UNIX, but - shouldn't be one on TOPS-20). - - Essentially because the PWD command returns the same type of - information as the successful MKD command, the successful PWD - command uses the 257 reply code as well. - - SUBTLETIES - - Because these commands will be most useful in transferring - subtrees from one machine to another, carefully observe that the - argument to MKD is to be interpreted as a sub-directory of the - current working directory, unless it contains enough information - for the destination host to tell otherwise. A hypothetical - example of its use in the TOPS-20 world: - - CWD - 200 Working directory changed - MKD overrainbow - 257 "" directory created - CWD overrainbow - 431 No such directory - CWD - 200 Working directory changed - - CWD - 200 Working directory changed to - MKD - 257 "" directory created - CWD - - Note that the first example results in a subdirectory of the - connected directory. In contrast, the argument in the second - example contains enough information for TOPS-20 to tell that the - - -Postel & Reynolds [Page 64] - - - -RFC 959 October 1985 -File Transfer Protocol - - - directory is a top-level directory. Note also that - in the first example the user "violated" the protocol by - attempting to access the freshly created directory with a name - other than the one returned by TOPS-20. Problems could have - resulted in this case had there been an directory; - this is an ambiguity inherent in some TOPS-20 implementations. - Similar considerations apply to the RMD command. The point is - this: except where to do so would violate a host's conventions for - denoting relative versus absolute pathnames, the host should treat - the operands of the MKD and RMD commands as subdirectories. The - 257 reply to the MKD command must always contain the absolute - pathname of the created directory. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 65] - - - -RFC 959 October 1985 -File Transfer Protocol - - -APPENDIX III - RFCs on FTP - - Bhushan, Abhay, "A File Transfer Protocol", RFC 114 (NIC 5823), - MIT-Project MAC, 16 April 1971. - - Harslem, Eric, and John Heafner, "Comments on RFC 114 (A File - Transfer Protocol)", RFC 141 (NIC 6726), RAND, 29 April 1971. - - Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 172 - (NIC 6794), MIT-Project MAC, 23 June 1971. - - Braden, Bob, "Comments on DTP and FTP Proposals", RFC 238 (NIC 7663), - UCLA/CCN, 29 September 1971. - - Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 265 - (NIC 7813), MIT-Project MAC, 17 November 1971. - - McKenzie, Alex, "A Suggested Addition to File Transfer Protocol", - RFC 281 (NIC 8163), BBN, 8 December 1971. - - Bhushan, Abhay, "The Use of "Set Data Type" Transaction in File - Transfer Protocol", RFC 294 (NIC 8304), MIT-Project MAC, - 25 January 1972. - - Bhushan, Abhay, "The File Transfer Protocol", RFC 354 (NIC 10596), - MIT-Project MAC, 8 July 1972. - - Bhushan, Abhay, "Comments on the File Transfer Protocol (RFC 354)", - RFC 385 (NIC 11357), MIT-Project MAC, 18 August 1972. - - Hicks, Greg, "User FTP Documentation", RFC 412 (NIC 12404), Utah, - 27 November 1972. - - Bhushan, Abhay, "File Transfer Protocol (FTP) Status and Further - Comments", RFC 414 (NIC 12406), MIT-Project MAC, 20 November 1972. - - Braden, Bob, "Comments on File Transfer Protocol", RFC 430 - (NIC 13299), UCLA/CCN, 7 February 1973. - - Thomas, Bob, and Bob Clements, "FTP Server-Server Interaction", - RFC 438 (NIC 13770), BBN, 15 January 1973. - - Braden, Bob, "Print Files in FTP", RFC 448 (NIC 13299), UCLA/CCN, - 27 February 1973. - - McKenzie, Alex, "File Transfer Protocol", RFC 454 (NIC 14333), BBN, - 16 February 1973. - - -Postel & Reynolds [Page 66] - - - -RFC 959 October 1985 -File Transfer Protocol - - - Bressler, Bob, and Bob Thomas, "Mail Retrieval via FTP", RFC 458 - (NIC 14378), BBN-NET and BBN-TENEX, 20 February 1973. - - Neigus, Nancy, "File Transfer Protocol", RFC 542 (NIC 17759), BBN, - 12 July 1973. - - Krilanovich, Mark, and George Gregg, "Comments on the File Transfer - Protocol", RFC 607 (NIC 21255), UCSB, 7 January 1974. - - Pogran, Ken, and Nancy Neigus, "Response to RFC 607 - Comments on the - File Transfer Protocol", RFC 614 (NIC 21530), BBN, 28 January 1974. - - Krilanovich, Mark, George Gregg, Wayne Hathaway, and Jim White, - "Comments on the File Transfer Protocol", RFC 624 (NIC 22054), UCSB, - Ames Research Center, SRI-ARC, 28 February 1974. - - Bhushan, Abhay, "FTP Comments and Response to RFC 430", RFC 463 - (NIC 14573), MIT-DMCG, 21 February 1973. - - Braden, Bob, "FTP Data Compression", RFC 468 (NIC 14742), UCLA/CCN, - 8 March 1973. - - Bhushan, Abhay, "FTP and Network Mail System", RFC 475 (NIC 14919), - MIT-DMCG, 6 March 1973. - - Bressler, Bob, and Bob Thomas "FTP Server-Server Interaction - II", - RFC 478 (NIC 14947), BBN-NET and BBN-TENEX, 26 March 1973. - - White, Jim, "Use of FTP by the NIC Journal", RFC 479 (NIC 14948), - SRI-ARC, 8 March 1973. - - White, Jim, "Host-Dependent FTP Parameters", RFC 480 (NIC 14949), - SRI-ARC, 8 March 1973. - - Padlipsky, Mike, "An FTP Command-Naming Problem", RFC 506 - (NIC 16157), MIT-Multics, 26 June 1973. - - Day, John, "Memo to FTP Group (Proposal for File Access Protocol)", - RFC 520 (NIC 16819), Illinois, 25 June 1973. - - Merryman, Robert, "The UCSD-CC Server-FTP Facility", RFC 532 - (NIC 17451), UCSD-CC, 22 June 1973. - - Braden, Bob, "TENEX FTP Problem", RFC 571 (NIC 18974), UCLA/CCN, - 15 November 1973. - - - - -Postel & Reynolds [Page 67] - - - -RFC 959 October 1985 -File Transfer Protocol - - - McKenzie, Alex, and Jon Postel, "Telnet and FTP Implementation - - Schedule Change", RFC 593 (NIC 20615), BBN and MITRE, - 29 November 1973. - - Sussman, Julie, "FTP Error Code Usage for More Reliable Mail - Service", RFC 630 (NIC 30237), BBN, 10 April 1974. - - Postel, Jon, "Revised FTP Reply Codes", RFC 640 (NIC 30843), - UCLA/NMC, 5 June 1974. - - Harvey, Brian, "Leaving Well Enough Alone", RFC 686 (NIC 32481), - SU-AI, 10 May 1975. - - Harvey, Brian, "One More Try on the FTP", RFC 691 (NIC 32700), SU-AI, - 28 May 1975. - - Lieb, J., "CWD Command of FTP", RFC 697 (NIC 32963), 14 July 1975. - - Harrenstien, Ken, "FTP Extension: XSEN", RFC 737 (NIC 42217), SRI-KL, - 31 October 1977. - - Harrenstien, Ken, "FTP Extension: XRSQ/XRCP", RFC 743 (NIC 42758), - SRI-KL, 30 December 1977. - - Lebling, P. David, "Survey of FTP Mail and MLFL", RFC 751, MIT, - 10 December 1978. - - Postel, Jon, "File Transfer Protocol Specification", RFC 765, ISI, - June 1980. - - Mankins, David, Dan Franklin, and Buzz Owen, "Directory Oriented FTP - Commands", RFC 776, BBN, December 1980. - - Padlipsky, Michael, "FTP Unique-Named Store Command", RFC 949, MITRE, - July 1985. - - - - - - - - - - - - - - -Postel & Reynolds [Page 68] - - - -RFC 959 October 1985 -File Transfer Protocol - - -REFERENCES - - [1] Feinler, Elizabeth, "Internet Protocol Transition Workbook", - Network Information Center, SRI International, March 1982. - - [2] Postel, Jon, "Transmission Control Protocol - DARPA Internet - Program Protocol Specification", RFC 793, DARPA, September 1981. - - [3] Postel, Jon, and Joyce Reynolds, "Telnet Protocol - Specification", RFC 854, ISI, May 1983. - - [4] Reynolds, Joyce, and Jon Postel, "Assigned Numbers", RFC 943, - ISI, April 1985. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Postel & Reynolds [Page 69] - diff --git a/lib/inets/doc/src/Makefile b/lib/inets/doc/src/Makefile index cbfa5c9e30..d66ef31ba6 100644 --- a/lib/inets/doc/src/Makefile +++ b/lib/inets/doc/src/Makefile @@ -43,12 +43,10 @@ XML_CHAPTER_FILES = \ inets_services.xml \ http_client.xml \ http_server.xml \ - ftp_client.xml \ notes.xml XML_REF3_FILES = \ inets.xml \ - ftp.xml \ tftp.xml \ http_uri.xml\ httpc.xml\ diff --git a/lib/inets/doc/src/ftp.xml b/lib/inets/doc/src/ftp.xml deleted file mode 100644 index 42bece4d38..0000000000 --- a/lib/inets/doc/src/ftp.xml +++ /dev/null @@ -1,948 +0,0 @@ - - - - -
- - 19972016 - Ericsson AB. All Rights Reserved. - - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - ftp - Peter Högfeldt - - 1997-11-05 - B - ftp.xml -
- ftp - A File Transfer Protocol client. - - - -

This module implements a client for file transfer - according to a subset of the File Transfer Protocol (FTP), see - RFC 959.

- -

As from Inets 4.4.1, the FTP - client always tries to use passive FTP mode and only resort - to active FTP mode if this fails. This default behavior can be - changed by start option mode.

- - - -

An FTP client can be started in two ways. One is using the - Inets service framework, - the other is to start it directly as a standalone process - using function open.

- -

For a simple example of an FTP session, see - Inets User's Guide.

- -

In addition to the ordinary functions for receiving and sending - files (see recv/2, recv/3, send/2, and - send/3) there are functions for receiving remote files as - binaries (see recv_bin/2) and for sending binaries to be - stored as remote files (see send_bin/3).

- -

A set of functions is provvided for sending and receiving - contiguous parts of a file to be stored in a remote file. For send, - see send_chunk_start/2, send_chunk/2, and - send_chunk_end/1. For receive, see - recv_chunk_start/2 and recv_chunk/).

- -

The return values of the following functions depend - much on the implementation of the FTP server at the remote - host. In particular, the results from ls and nlist - varies. Often real errors are not reported as errors by ls, - even if, for example, a file or directory does not - exist. nlist is usually more strict, but some - implementations have the peculiar behaviour of responding with an - error if the request is a listing of the contents of a directory - that exists but is empty.

- - -
- -
- FTP CLIENT SERVICE START/STOP - -

The FTP client can be started and stopped dynamically in runtime by - calling the Inets application API - inets:start(ftpc, ServiceConfig), - or inets:start(ftpc, ServiceConfig, How), and - inets:stop(ftpc, Pid). - For details, see inets(3).

- -

The available configuration options are as follows:

- - - {host, Host} - - -

Host = string() | ip_address()

-
- - {port, Port} - - -

Port = integer() > 0

-

Default is 21.

-
- - {mode, Mode} - - -

Mode = active | passive

-

Default is passive.

-
- - {verbose, Verbose} - - -

Verbose = boolean()

-

Determines if the FTP communication is to be - verbose or not.

-

Default is false.

-
- - {debug, Debug} - - -

Debug = trace | debug | disable

-

Debugging using the dbg toolkit.

-

Default is disable.

-
- - {ipfamily, IpFamily} - - -

IpFamily = inet | inet6 | inet6fb4

-

With inet6fb4 the client behaves as before, that is, - tries to use IPv6, and only if that does not work it - uses IPv4).

-

Default is inet (IPv4).

-
- - {timeout, Timeout} - - -

Timeout = non_neg_integer()

-

Connection time-out.

-

Default is 60000 (milliseconds).

-
- - {dtimeout, DTimeout} - - -

DTimeout = non_neg_integer() | infinity

-

Data connect time-out. - The time the client waits for the server to connect to the - data socket.

-

Default is infinity.

-
- - {progress, Progress} - - -

Progress = ignore | {CBModule, CBFunction, InitProgress}

-

CBModule = atom(), CBFunction = atom()

-

InitProgress = term()

-

Default is ignore.

-
- -
- -

Option progress is intended to be used by applications that - want to create some type of progress report, such as a progress bar in - a GUI. Default for the progress option is ignore, - that is, the option is not used. When the progress option is - specified, the following happens when ftp:send/[3,4] or - ftp:recv/[3,4] are called:

- - - -

Before a file is transferred, the following call is - made to indicate the start of the file transfer and how large - the file is. The return value of the callback function - is to be a new value for the UserProgressTerm that will - be used as input the next time the callback function is - called.

-

- CBModule:CBFunction(InitProgress, File, {file_size, FileSize}) -

-
- - -

Every time a chunk of bytes is transferred the - following call is made:

-

- CBModule:CBFunction(UserProgressTerm, File, {transfer_size, TransferSize}) -

-
- - -

At the end of the file the following call is - made to indicate the end of the transfer:

-

- CBModule:CBFunction(UserProgressTerm, File, {transfer_size, 0}) -

-
-
- -

The callback function is to be defined as follows:

- -

- CBModule:CBFunction(UserProgressTerm, File, Size) -> UserProgressTerm -

- -

- CBModule = CBFunction = atom() -

- -

- UserProgressTerm = term() -

- -

- File = string() -

- -

- Size = {transfer_size, integer()} | {file_size, integer()} | {file_size, unknown} -

- -

For remote files, ftp cannot determine the - file size in a platform independent way. In this case the size - becomes unknown and it is left to the application to - determine the size.

- - -

The callback is made by a middleman process, hence the - file transfer is not affected by the code in the progress - callback function. If the callback crashes, this is - detected by the FTP connection process, which then prints an - info-report and goes on as if the progress option was set - to ignore.

-
- -

The file transfer type is set to the default of the FTP server - when the session is opened. This is usually ASCCI mode. -

- -

The current local working directory (compare lpwd/1) is set - to the value reported by file:get_cwd/1, the wanted - local directory. -

- -

The return value Pid is used as a reference to the - newly created FTP client in all other functions, and they are to - be called by the process that created the connection. The FTP - client process monitors the process that created it and - terminates if that process terminates.

-
- -
- DATA TYPES -

The following type definitions are used by more than one - function in the FTP client API:

-

pid() = identifier of an FTP connection

-

string() = list of ASCII characters

-

shortage_reason() = etnospc | epnospc

-

restriction_reason() = epath | efnamena | elogin | enotbinary - - all restrictions are not always relevant to all functions -

-

common_reason() = econn | eclosed | term() - - some explanation of what went wrong

- - -
- - - - account(Pid, Account) -> ok | {error, Reason} - Specifies which account to use. - - Pid = pid() - Account = string() - Reason = eacct | common_reason() - - -

Sets the account for an operation, if needed.

- - - - -
-
- - - append(Pid, LocalFile) -> - append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason} - Transfers a file to remote server, and appends it to - Remotefile. - - Pid = pid() - LocalFile = RemoteFile = string() - Reason = epath | elogin | etnospc | epnospc | efnamena | common_reason - - -

Transfers the file LocalFile to the remote server. If - RemoteFile is specified, the name of the remote file that the - file is appended to is set to RemoteFile, otherwise - to LocalFile. If the file does not exists, - it is created.

- - -
-
- - - append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason} - Transfers a binary into a remote file. - - Pid = pid() - Bin = binary()() - RemoteFile = string() - Reason = restriction_reason()| shortage_reason() | common_reason() - - -

Transfers the binary Bin to the remote server and appends - it to the file RemoteFile. If the file does not exist, it - is created.

- - -
-
- - - append_chunk(Pid, Bin) -> ok | {error, Reason} - Appends a chunk to the remote file. - - Pid = pid() - Bin = binary() - Reason = echunk | restriction_reason() | common_reason() - - -

Transfers the chunk Bin to the remote server, which - appends it to the file specified in the call to - append_chunk_start/2.

-

For some errors, for example, file system full, it is - necessary to call append_chunk_end to get the - proper reason.

- - -
-
- - - append_chunk_start(Pid, File) -> ok | {error, Reason} - Starts transfer of file chunks for appending to File. - - Pid = pid() - File = string() - Reason = restriction_reason() | common_reason() - - -

Starts the transfer of chunks for appending to the file - File at the remote server. If the file does not exist, - it is created.

- - -
-
- - - append_chunk_end(Pid) -> ok | {error, Reason} - Stops transfer of chunks for appending. - - Pid = pid() - Reason = echunk | restriction_reason() | shortage_reason() - - -

Stops transfer of chunks for appending to the remote server. - The file at the remote server, specified in the call to - append_chunk_start/2, is closed by the server.

- - -
-
- - - cd(Pid, Dir) -> ok | {error, Reason} - Changes remote working directory. - - Pid = pid() - Dir = string() - Reason = restriction_reason() | common_reason() - - -

Changes the working directory at the remote server to - Dir.

- - -
-
- - - close(Pid) -> ok - Ends the FTP session. - - Pid = pid() - - -

Ends an FTP session, created using function - open.

- - -
-
- - - delete(Pid, File) -> ok | {error, Reason} - Deletes a file at the remote server. - - Pid = pid() - File = string() - Reason = restriction_reason() | common_reason() - - -

Deletes the file File at the remote server.

- - -
-
- - - formaterror(Tag) -> string() - Returns error diagnostics. - - Tag = {error, atom()} | atom() - - -

Given an error return value {error, AtomReason}, - this function returns a readable string describing the error.

- - -
-
- - - lcd(Pid, Dir) -> ok | {error, Reason} - Changes local working directory. - - Pid = pid() - Dir = string() - Reason = restriction_reason() - - -

Changes the working directory to Dir for the local client.

- - -
-
- - - lpwd(Pid) -> {ok, Dir} - Gets local current working directory. - - Pid = pid() - - -

Returns the current working directory at the local client.

- - - - -
-
- - - ls(Pid) -> - ls(Pid, Pathname) -> {ok, Listing} | {error, Reason} - List of files. - - Pid = pid() - Pathname = string() - Listing = string() - Reason = restriction_reason() | common_reason() - - -

Returns a list of files in long format.

-

Pathname can be a directory, a group of files, or - a file. The Pathname string can contain wildcards.

-

ls/1 implies the current remote directory of the user.

-

The format of Listing depends on the operating system. - On UNIX, it is typically produced from the output of the - ls -l shell command.

- - -
-
- - - mkdir(Pid, Dir) -> ok | {error, Reason} - Creates a remote directory. - - Pid = pid() - Dir = string() - Reason = restriction_reason() | common_reason() - - -

Creates the directory Dir at the remote server.

- - - - -
-
- - - nlist(Pid) -> - nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason} - List of files. - - Pid = pid() - Pathname = string() - Listing = string() - Reason = restriction_reason() | common_reason() - - -

Returns a list of files in short format.

-

Pathname can be a directory, a group of files, or - a file. The Pathname string can contain wildcards.

-

nlist/1 implies the current remote directory of the user.

-

The format of Listing is a stream of - filenames where each filename is separated by <CRLF> or - <NL>. Contrary to function ls, the purpose of - nlist is to enable a program to - process filename information automatically.

- - -
-
- - - open(Host) -> {ok, Pid} | {error, Reason} - open(Host, Opts) -> {ok, Pid} | {error, Reason} - Starts a standalone FTP client. - - Host = string() | ip_address() - Opts = options() - options() = [option()] - option() = start_option() | open_option() - start_option() = {verbose, verbose()} | {debug, debug()} - verbose() = boolean() (default is false) - debug() = disable | debug | trace (default is disable) - open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress()} - ipfamily() = inet | inet6 | inet6fb4 (default is inet) - port() = integer() > 0 (default is 21) - mode() = active | passive (default is passive) - tls_options() = [ssl:ssloption()] - timeout() = integer() > 0 (default is 60000 milliseconds) - dtimeout() = integer() > 0 | infinity (default is infinity) - pogress() = ignore | {module(), function(), initial_data()} (default is ignore) - module() = atom() - function() = atom() - initial_data() = term() - Reason = ehost | term() - - - -

Starts a standalone FTP client process - (without the Inets service framework) and - opens a session with the FTP server at Host.

- -

If option {tls, tls_options()} is present, the FTP session - is transported over tls (ftps, see - RFC 4217). - The list tls_options() can be empty. The function - ssl:connect/3 - is used for securing both the control connection and the data sessions. -

- -

A session opened in this way is closed using function - close.

- - -
-
- - - pwd(Pid) -> {ok, Dir} | {error, Reason} - Gets the remote current working directory. - - Pid = pid() - Reason = restriction_reason() | common_reason() - - -

Returns the current working directory at the remote server.

- - - - -
-
- - - recv(Pid, RemoteFile) -> - recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason} - Transfers a file from remote server. - - Pid = pid() - RemoteFile = LocalFile = string() - Reason = restriction_reason() | common_reason() | file_write_error_reason() - file_write_error_reason() = see file:write/2 - - -

Transfers the file RemoteFile from the remote server - to the file system of the local client. If - LocalFile is specified, the local file will be - LocalFile, otherwise - RemoteFile.

-

If the file write fails (for example, enospc), the command is - aborted and {error, file_write_error_reason()} is returned. - However, the file is not removed.

- - -
-
- - - recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason} - Transfers a file from remote server as a binary. - - Pid = pid() - Bin = binary() - RemoteFile = string() - Reason = restriction_reason() | common_reason() - - -

Transfers the file RemoteFile from the remote server and - receives it as a binary.

- - -
-
- - - recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason} - Starts chunk-reading of the remote file. - - Pid = pid() - RemoteFile = string() - Reason = restriction_reason() | common_reason() - - -

Starts transfer of the file RemoteFile from the - remote server.

- - -
-
- - - recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason} - Receives a chunk of the remote file. - - Pid = pid() - Bin = binary() - Reason = restriction_reason() | common_reason() - - -

Receives a chunk of the remote file (RemoteFile of - recv_chunk_start). The return values have the following - meaning:

- - ok = the transfer is complete. - {ok, Bin} = just another chunk of the file. - {error, Reason} = transfer failed. - - - -
-
- - - rename(Pid, Old, New) -> ok | {error, Reason} - Renames a file at the remote server. - - Pid = pid() - CurrFile = NewFile = string() - Reason = restriction_reason() | common_reason() - - -

Renames Old to New at the remote server.

- - -
-
- - - rmdir(Pid, Dir) -> ok | {error, Reason} - Removes a remote directory. - - Pid = pid() - Dir = string() - Reason = restriction_reason() | common_reason() - - -

Removes directory Dir at the remote server.

- - - - -
-
- - - send(Pid, LocalFile) -> - send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason} - Transfers a file to the remote server. - - Pid = pid() - LocalFile = RemoteFile = string() - Reason = restriction_reason() | common_reason() | shortage_reason() - - -

Transfers the file LocalFile to the remote server. If - RemoteFile is specified, the name of the remote file is set - to RemoteFile, otherwise to LocalFile.

- - -
-
- - - send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason} - Transfers a binary into a remote file. - - Pid = pid() - Bin = binary()() - RemoteFile = string() - Reason = restriction_reason() | common_reason() | shortage_reason() - - -

Transfers the binary Bin into the file RemoteFile - at the remote server.

- - -
-
- - - send_chunk(Pid, Bin) -> ok | {error, Reason} - Writes a chunk to the remote file. - - Pid = pid() - Bin = binary() - Reason = echunk | restriction_reason() | common_reason() - - -

Transfers the chunk Bin to the remote server, which - writes it into the file specified in the call to - send_chunk_start/2.

-

For some errors, for example, file system full, it is - necessary to to call send_chunk_end to get the - proper reason.

- - -
-
- - - send_chunk_start(Pid, File) -> ok | {error, Reason} - Starts transfer of file chunks. - - Pid = pid() - File = string() - Reason = restriction_reason() | common_reason() - - -

Starts transfer of chunks into the file File at the - remote server.

- - -
-
- - - send_chunk_end(Pid) -> ok | {error, Reason} - Stops transfer of chunks. - - Pid = pid() - Reason = restriction_reason() | common_reason() | shortage_reason() - - -

Stops transfer of chunks to the remote server. The file at the - remote server, specified in the call to send_chunk_start/2 - is closed by the server.

- - -
-
- - - type(Pid, Type) -> ok | {error, Reason} - Sets transfer type to asciior binary. - - Pid = pid() - Type = ascii | binary - Reason = etype | restriction_reason() | common_reason() - - -

Sets the file transfer type to ascii or binary. When - an FTP session is opened, the default transfer type of the - server is used, most often ascii, which is default - according to RFC 959.

- -
-
- - - user(Pid, User, Password) -> ok | {error, Reason} - User login. - - Pid = pid() - User = Password = string() - Reason = euser | common_reason() - - -

Performs login of User with Password.

- - -
-
- - - user(Pid, User, Password, Account) -> ok | {error, Reason} - User login. - - Pid = pid() - User = Password = string() - Reason = euser | common_reason() - - -

Performs login of User with Password to the account - specified by Account.

- - -
-
- - - quote(Pid, Command) -> [FTPLine] - Sends an arbitrary FTP command. - - Pid = pid() - Command = string() - FTPLine = string( - -

The telnet end of line characters, from the FTP - protocol definition, CRLF, for example, "\\r\\n" has been removed.

-

Sends an arbitrary FTP command and returns verbatim a list - of the lines sent back by the FTP server. This function is - intended to give application accesses to FTP commands - that are server-specific or that cannot be provided by - this FTP client.

- -

FTP commands requiring a data connection cannot be - successfully issued with this function.

-
-
-
-
- -
- ERRORS -

The possible error reasons and the corresponding diagnostic strings - returned by formaterror/1 are as follows: -

- - echunk - -

Synchronization error during chunk sending according to one - of the following: -

- A call is made to send_chunk/2 or send_chunk_end/1 - before a call to send_chunk_start/2. - A call has been made to another transfer function during chunk - sending, that is, before a call to send_chunk_end/1. - -
- eclosed - -

The session is closed.

-
- econn - -

Connection to the remote server is prematurely closed.

-
- ehost - -

Host is not found, FTP server is not found, or connection is rejected - by FTP server.

-
- elogin - -

User is not logged in.

-
- enotbinary - -

Term is not a binary.

-
- epath - -

No such file or directory, or directory already exists, or - permission denied.

-
- etype - -

No such type.

-
- euser - -

Invalid username or password.

-
- etnospc - -

Insufficient storage space in system [452].

-
- epnospc - -

Exceeded storage allocation (for current directory or - dataset) [552].

-
- efnamena - -

Filename not allowed [553].

-
-
-
- -
- SEE ALSO -

file(3) - filename(3) - and J. Postel and J. Reynolds: File Transfer Protocol - (RFC 959). -

-
- -
- - diff --git a/lib/inets/doc/src/ftp_client.xml b/lib/inets/doc/src/ftp_client.xml deleted file mode 100644 index 990dd68604..0000000000 --- a/lib/inets/doc/src/ftp_client.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - -
- - 20042016 - Ericsson AB. All Rights Reserved. - - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - FTP Client - Ingela Anderton Andin - - - - - - - ftp_client.xml -
- -
- Getting Started - -

FTP clients are considered to be rather temporary. Thus, - they are only started and stopped during runtime and cannot - be started at application startup. - The FTP client API is designed to allow some functions to - return intermediate results. This implies that only the process - that started the FTP client can access it with - preserved sane semantics. - If the process that started the FTP session - dies, the FTP client process terminates.

- -

The client supports IPv6 as long as the underlying mechanisms - also do so.

- -

The following is a simple example of an FTP session, where - the user guest with password password logs on to - the remote host erlang.org:

- inets:start(). - ok - 2> {ok, Pid} = inets:start(ftpc, [{host, "erlang.org"}]). - {ok,<0.22.0>} - 3> ftp:user(Pid, "guest", "password"). - ok - 4> ftp:pwd(Pid). - {ok, "/home/guest"} - 5> ftp:cd(Pid, "appl/examples"). - ok - 6> ftp:lpwd(Pid). - {ok, "/home/fred"}. - 7> ftp:lcd(Pid, "/home/eproj/examples"). - ok - 8> ftp:recv(Pid, "appl.erl"). - ok - 9> inets:stop(ftpc, Pid). - ok - ]]> -

The file - appl.erl is transferred from the remote to the local - host. When the session is opened, the current directory at - the remote host is /home/guest, and /home/fred - at the local host. Before transferring the file, the current - local directory is changed to /home/eproj/examples, and - the remote directory is set to - /home/guest/appl/examples.

-
-
- - diff --git a/lib/inets/doc/src/introduction.xml b/lib/inets/doc/src/introduction.xml index 1af2ef5dae..c006599a21 100644 --- a/lib/inets/doc/src/introduction.xml +++ b/lib/inets/doc/src/introduction.xml @@ -4,7 +4,7 @@
- 19972016 + 19972018 Ericsson AB. All Rights Reserved. @@ -22,12 +22,12 @@ Introduction - Ingela Anderton Andin + Péter Dimitrov - 2004-09-28 + 2018-02-28 A introduction.xml
@@ -37,7 +37,6 @@

Inets is a container for Internet clients and servers including the following:

- An FTP client A TFTP client and server An client and server @@ -50,7 +49,7 @@ Prerequisites

It is assumed that the reader is familiar with the Erlang programming language, concepts of OTP, and has a basic - understanding of the FTP, TFTP, and HTTP protocols.

+ understanding of the TFTP and HTTP protocols.

diff --git a/lib/inets/doc/src/part.xml b/lib/inets/doc/src/part.xml index f777481b5c..e7a786d47b 100644 --- a/lib/inets/doc/src/part.xml +++ b/lib/inets/doc/src/part.xml @@ -4,7 +4,7 @@
- 20042016 + 20042018 Ericsson AB. All Rights Reserved. @@ -23,9 +23,9 @@ Inets User's Guide - Ingela Anderton Andin + Péter Dimitrov - 2002-09-17 + 2018-02-28 A part.sgml
@@ -33,7 +33,6 @@

The Inets application provides a set of Internet-related services as follows:

- An FTP client A TFTP client and server An client and server @@ -43,7 +42,6 @@ -
diff --git a/lib/inets/doc/src/ref_man.xml b/lib/inets/doc/src/ref_man.xml index 27021ea09a..33886d1a41 100644 --- a/lib/inets/doc/src/ref_man.xml +++ b/lib/inets/doc/src/ref_man.xml @@ -4,7 +4,7 @@
- 19972015 + 19972018 Ericsson AB. All Rights Reserved. @@ -23,19 +23,18 @@ Inets Reference Manual - Joakim Grebenö + Péter Dimitrov - 1997-07-16 + 2018-02-28 2.1 ref_man.xml

Inets is a container for Internet clients and - servers. An FTP client, an HTTP client and server, and + servers. An HTTP client and server, and a TFTP client and server are incorporated in Inets.

- diff --git a/lib/inets/src/ftp/Makefile b/lib/inets/src/ftp/Makefile deleted file mode 100644 index 6b99694ea7..0000000000 --- a/lib/inets/src/ftp/Makefile +++ /dev/null @@ -1,104 +0,0 @@ -# -# %CopyrightBegin% -# -# Copyright Ericsson AB 2005-2016. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# %CopyrightEnd% -# -# - -include $(ERL_TOP)/make/target.mk -EBIN = ../../ebin -include $(ERL_TOP)/make/$(TARGET)/otp.mk - - -# ---------------------------------------------------- -# Application version -# ---------------------------------------------------- -include ../../vsn.mk - -VSN = $(INETS_VSN) - - -# ---------------------------------------------------- -# Release directory specification -# ---------------------------------------------------- -RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) - - -# ---------------------------------------------------- -# Target Specs -# ---------------------------------------------------- -MODULES = \ - ftp \ - ftp_progress \ - ftp_response \ - ftp_sup - -HRL_FILES = ftp_internal.hrl - -ERL_FILES = $(MODULES:%=%.erl) - -TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) - - -# ---------------------------------------------------- -# FLAGS -# ---------------------------------------------------- - -include ../inets_app/inets.mk - -ifeq ($(FTP_DEBUG),true) - INETS_FLAGS += -Dftp_debug -endif - -ERL_COMPILE_FLAGS += \ - $(INETS_FLAGS) \ - $(INETS_ERL_COMPILE_FLAGS) \ - -I../../include \ - -I../inets_app - - -# ---------------------------------------------------- -# Targets -# ---------------------------------------------------- - -debug opt: $(TARGET_FILES) - -clean: - rm -f $(TARGET_FILES) - rm -f core - -docs: - -# ---------------------------------------------------- -# Release Target -# ---------------------------------------------------- -include $(ERL_TOP)/make/otp_release_targets.mk - -release_spec: opt - $(INSTALL_DIR) "$(RELSYSDIR)/src" - $(INSTALL_DIR) "$(RELSYSDIR)/src/ftp" - $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) "$(RELSYSDIR)/src/ftp" - $(INSTALL_DIR) "$(RELSYSDIR)/ebin" - $(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)/ebin" - -release_docs_spec: - -info: - @echo "APPLICATION = $(APPLICATION)" - @echo "INETS_DEBUG = $(INETS_DEBUG)" - @echo "INETS_FLAGS = $(INETS_FLAGS)" - @echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)" diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl deleted file mode 100644 index e0430654eb..0000000000 --- a/lib/inets/src/ftp/ftp.erl +++ /dev/null @@ -1,2596 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2017. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% -%% Description: This module implements an ftp client, RFC 959. -%% It also supports ipv6 RFC 2428 and starttls RFC 4217. - --module(ftp). - --behaviour(gen_server). --behaviour(inets_service). - - -%% API - Client interface --export([cd/2, close/1, delete/2, formaterror/1, - lcd/2, lpwd/1, ls/1, ls/2, - mkdir/2, nlist/1, nlist/2, - open/1, open/2, - pwd/1, quote/2, - recv/2, recv/3, recv_bin/2, - recv_chunk_start/2, recv_chunk/1, - rename/3, rmdir/2, - send/2, send/3, send_bin/3, - send_chunk_start/2, send_chunk/2, send_chunk_end/1, - type/2, user/3, user/4, account/2, - append/3, append/2, append_bin/3, - append_chunk/2, append_chunk_end/1, append_chunk_start/2, - info/1, latest_ctrl_response/1]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, - handle_info/2, terminate/2, code_change/3]). - -%% supervisor callbacks --export([start_link/1, start_link/2]). - -%% Behavior callbacks --export([start_standalone/1, start_service/1, - stop_service/1, services/0, service_info/1]). - --include("ftp_internal.hrl"). - -%% Constants used in internal state definition --define(CONNECTION_TIMEOUT, 60*1000). --define(DATA_ACCEPT_TIMEOUT, infinity). --define(DEFAULT_MODE, passive). --define(PROGRESS_DEFAULT, ignore). --define(FTP_EXT_DEFAULT, false). - -%% Internal Constants --define(FTP_PORT, 21). --define(FILE_BUFSIZE, 4096). - -%% Internal state --record(state, { - csock = undefined, % socket() - Control connection socket - dsock = undefined, % socket() - Data connection socket - tls_options = undefined, % list() - verbose = false, % boolean() - ldir = undefined, % string() - Current local directory - type = ftp_server_default, % atom() - binary | ascii - chunk = false, % boolean() - Receiving data chunks - mode = ?DEFAULT_MODE, % passive | active - timeout = ?CONNECTION_TIMEOUT, % integer() - %% Data received so far on the data connection - data = <<>>, % binary() - %% Data received so far on the control connection - %% {BinStream, AccLines}. If a binary sequence - %% ends with ?CR then keep it in the binary to - %% be able to detect if the next received byte is ?LF - %% and hence the end of the response is reached! - ctrl_data = {<<>>, [], start}, % {binary(), [bytes()], LineStatus} - %% pid() - Client pid (note not the same as "From") - latest_ctrl_response = "", - owner = undefined, - client = undefined, % "From" to be used in gen_server:reply/2 - %% Function that activated a connection and maybe some - %% data needed further on. - caller = undefined, % term() - ipfamily, % inet | inet6 | inet6fb4 - progress = ignore, % ignore | pid() - dtimeout = ?DATA_ACCEPT_TIMEOUT, % non_neg_integer() | infinity - tls_upgrading_data_connection = false, - ftp_extension = ?FTP_EXT_DEFAULT - }). - --record(recv_chunk_closing, { - dconn_closed = false, - pos_compl_received = false, - client_called_us = false - }). - - --type shortage_reason() :: 'etnospc' | 'epnospc'. --type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'. --type common_reason() :: 'econn' | 'eclosed' | term(). --type file_write_error_reason() :: term(). % See file:write for more info - --define(DBG(F,A), 'n/a'). -%%-define(DBG(F,A), io:format(F,A)). -%%-define(DBG(F,A), ct:pal("~p:~p " ++ if is_list(F) -> F; is_atom(F) -> atom_to_list(F) end, [?MODULE,?LINE|A])). - -%%%========================================================================= -%%% API - CLIENT FUNCTIONS -%%%========================================================================= - -%%-------------------------------------------------------------------------- -%% open(HostOrOtpList, , ) -> {ok, Pid} | {error, ehost} -%% HostOrOtpList = string() | [{option_list, Options}] -%% Port = integer(), -%% Flags = [Flag], -%% Flag = verbose | debug | trace -%% -%% Description: Start an ftp client and connect to a host. -%%-------------------------------------------------------------------------- - --spec open(Host :: string() | inet:ip_address()) -> - {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}. - -%% -open({option_list, Options}) when is_list(Options) -> - try - {ok, StartOptions} = start_options(Options), - {ok, OpenOptions} = open_options(Options), - case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of - {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); - Error1 -> - Error1 - end - catch - throw:Error2 -> - Error2 - end; -%% - -open(Host) -> - open(Host, []). - --spec open(Host :: string() | inet:ip_address(), Opts :: list()) -> - {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}. - -%% -open(Host, Port) when is_integer(Port) -> - open(Host, [{port, Port}]); -%% - -open(Host, Opts) when is_list(Opts) -> - ?fcrt("open", [{host, Host}, {opts, Opts}]), - try - {ok, StartOptions} = start_options(Opts), - ?fcrt("open", [{start_options, StartOptions}]), - {ok, OpenOptions} = open_options([{host, Host}|Opts]), - ?fcrt("open", [{open_options, OpenOptions}]), - case start_link(StartOptions, []) of - {ok, Pid} -> - do_open(Pid, OpenOptions, tls_options(Opts)); - Error1 -> - ?fcrt("open - error", [{error1, Error1}]), - Error1 - end - catch - throw:Error2 -> - ?fcrt("open - error", [{error2, Error2}]), - Error2 - end. - -do_open(Pid, OpenOptions, TLSOpts) -> - case call(Pid, {open, ip_comm, OpenOptions}, plain) of - {ok, Pid} -> - maybe_tls_upgrade(Pid, TLSOpts); - Error -> - Error - end. -%%-------------------------------------------------------------------------- -%% user(Pid, User, Pass, ) -> ok | {error, euser} | {error, econn} -%% | {error, eacct} -%% Pid = pid(), -%% User = Pass = Acc = string() -%% -%% Description: Login with or without a supplied account name. -%%-------------------------------------------------------------------------- --spec user(Pid :: pid(), - User :: string(), - Pass :: string()) -> - 'ok' | {'error', Reason :: 'euser' | common_reason()}. - -user(Pid, User, Pass) -> - case {is_name_sane(User), is_name_sane(Pass)} of - {true, true} -> - call(Pid, {user, User, Pass}, atom); - _ -> - {error, euser} - end. - --spec user(Pid :: pid(), - User :: string(), - Pass :: string(), - Acc :: string()) -> - 'ok' | {'error', Reason :: 'euser' | common_reason()}. - -user(Pid, User, Pass, Acc) -> - case {is_name_sane(User), is_name_sane(Pass), is_name_sane(Acc)} of - {true, true, true} -> - call(Pid, {user, User, Pass, Acc}, atom); - _ -> - {error, euser} - end. - - -%%-------------------------------------------------------------------------- -%% account(Pid, Acc) -> ok | {error, eacct} -%% Pid = pid() -%% Acc= string() -%% -%% Description: Set a user Account. -%%-------------------------------------------------------------------------- - --spec account(Pid :: pid(), Acc :: string()) -> - 'ok' | {'error', Reason :: 'eacct' | common_reason()}. - -account(Pid, Acc) -> - case is_name_sane(Acc) of - true -> - call(Pid, {account, Acc}, atom); - _ -> - {error, eacct} - end. - - -%%-------------------------------------------------------------------------- -%% pwd(Pid) -> {ok, Dir} | {error, elogin} | {error, econn} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Get the current working directory at remote server. -%%-------------------------------------------------------------------------- - --spec pwd(Pid :: pid()) -> - {'ok', Dir :: string()} | - {'error', Reason :: restriction_reason() | common_reason()}. - -pwd(Pid) -> - call(Pid, pwd, ctrl). - - -%%-------------------------------------------------------------------------- -%% lpwd(Pid) -> {ok, Dir} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Get the current working directory at local server. -%%-------------------------------------------------------------------------- - --spec lpwd(Pid :: pid()) -> - {'ok', Dir :: string()}. - -lpwd(Pid) -> - call(Pid, lpwd, string). - - -%%-------------------------------------------------------------------------- -%% cd(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Change current working directory at remote server. -%%-------------------------------------------------------------------------- - --spec cd(Pid :: pid(), Dir :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. - -cd(Pid, Dir) -> - case is_name_sane(Dir) of - true -> - call(Pid, {cd, Dir}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% lcd(Pid, Dir) -> ok | {error, epath} -%% Pid = pid() -%% Dir = string() -%% -%% Description: Change current working directory for the local client. -%%-------------------------------------------------------------------------- - --spec lcd(Pid :: pid(), Dir :: string()) -> - 'ok' | {'error', Reason :: restriction_reason()}. - -lcd(Pid, Dir) -> - call(Pid, {lcd, Dir}, string). - - -%%-------------------------------------------------------------------------- -%% ls(Pid) -> Result -%% ls(Pid, ) -> Result -%% -%% Pid = pid() -%% Dir = string() -%% Result = {ok, Listing} | {error, Reason} -%% Listing = string() -%% Reason = epath | elogin | econn -%% -%% Description: Returns a list of files in long format. -%%-------------------------------------------------------------------------- - --spec ls(Pid :: pid()) -> - {'ok', Listing :: string()} | - {'error', Reason :: restriction_reason() | common_reason()}. - -ls(Pid) -> - ls(Pid, ""). - --spec ls(Pid :: pid(), Dir :: string()) -> - {'ok', Listing :: string()} | - {'error', Reason :: restriction_reason() | common_reason()}. - -ls(Pid, Dir) -> - case is_name_sane(Dir) of - true -> - call(Pid, {dir, long, Dir}, string); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% nlist(Pid) -> Result -%% nlist(Pid, Pathname) -> Result -%% -%% Pid = pid() -%% Pathname = string() -%% Result = {ok, Listing} | {error, Reason} -%% Listing = string() -%% Reason = epath | elogin | econn -%% -%% Description: Returns a list of files in short format -%%-------------------------------------------------------------------------- - --spec nlist(Pid :: pid()) -> - {'ok', Listing :: string()} | - {'error', Reason :: restriction_reason() | common_reason()}. - -nlist(Pid) -> - nlist(Pid, ""). - --spec nlist(Pid :: pid(), Pathname :: string()) -> - {'ok', Listing :: string()} | - {'error', Reason :: restriction_reason() | common_reason()}. - -nlist(Pid, Dir) -> - case is_name_sane(Dir) of - true -> - call(Pid, {dir, short, Dir}, string); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% rename(Pid, Old, New) -> ok | {error, epath} | {error, elogin} -%% | {error, econn} -%% Pid = pid() -%% CurrFile = NewFile = string() -%% -%% Description: Rename a file at remote server. -%%-------------------------------------------------------------------------- - --spec rename(Pid :: pid(), Old :: string(), New :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. - -rename(Pid, Old, New) -> - case {is_name_sane(Old), is_name_sane(New)} of - {true, true} -> - call(Pid, {rename, Old, New}, string); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% delete(Pid, File) -> ok | {error, epath} | {error, elogin} | -%% {error, econn} -%% Pid = pid() -%% File = string() -%% -%% Description: Remove file at remote server. -%%-------------------------------------------------------------------------- - --spec delete(Pid :: pid(), File :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. - -delete(Pid, File) -> - case is_name_sane(File) of - true -> - call(Pid, {delete, File}, string); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% mkdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} -%% Pid = pid(), -%% Dir = string() -%% -%% Description: Make directory at remote server. -%%-------------------------------------------------------------------------- - --spec mkdir(Pid :: pid(), Dir :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. - -mkdir(Pid, Dir) -> - case is_name_sane(Dir) of - true -> - call(Pid, {mkdir, Dir}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% rmdir(Pid, Dir) -> ok | {error, epath} | {error, elogin} | {error, econn} -%% Pid = pid(), -%% Dir = string() -%% -%% Description: Remove directory at remote server. -%%-------------------------------------------------------------------------- - --spec rmdir(Pid :: pid(), Dir :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. - -rmdir(Pid, Dir) -> - case is_name_sane(Dir) of - true -> - call(Pid, {rmdir, Dir}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% type(Pid, Type) -> ok | {error, etype} | {error, elogin} | {error, econn} -%% Pid = pid() -%% Type = ascii | binary -%% -%% Description: Set transfer type. -%%-------------------------------------------------------------------------- - --spec type(Pid :: pid(), Type :: ascii | binary) -> - 'ok' | - {'error', Reason :: 'etype' | restriction_reason() | common_reason()}. - -type(Pid, Type) -> - call(Pid, {type, Type}, atom). - - -%%-------------------------------------------------------------------------- -%% recv(Pid, RemoteFileName [, LocalFileName]) -> ok | {error, epath} | -%% {error, elogin} | {error, econn} -%% Pid = pid() -%% RemoteFileName = LocalFileName = string() -%% -%% Description: Transfer file from remote server. -%%-------------------------------------------------------------------------- - --spec recv(Pid :: pid(), RemoteFileName :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | - common_reason() | - file_write_error_reason()}. - -recv(Pid, RemotFileName) -> - recv(Pid, RemotFileName, RemotFileName). - --spec recv(Pid :: pid(), - RemoteFileName :: string(), - LocalFileName :: string()) -> - 'ok' | {'error', Reason :: term()}. - -recv(Pid, RemotFileName, LocalFileName) -> - case is_name_sane(RemotFileName) of - true -> - call(Pid, {recv, RemotFileName, LocalFileName}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, epath} | {error, elogin} -%% | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% Bin = binary() -%% -%% Description: Transfer file from remote server into binary. -%%-------------------------------------------------------------------------- - --spec recv_bin(Pid :: pid(), - RemoteFile :: string()) -> - {'ok', Bin :: binary()} | - {'error', Reason :: restriction_reason() | common_reason()}. - -recv_bin(Pid, RemoteFile) -> - case is_name_sane(RemoteFile) of - true -> - call(Pid, {recv_bin, RemoteFile}, bin); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% recv_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} -%% | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Start receive of chunks of remote file. -%%-------------------------------------------------------------------------- - --spec recv_chunk_start(Pid :: pid(), - RemoteFile :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. - -recv_chunk_start(Pid, RemoteFile) -> - case is_name_sane(RemoteFile) of - true -> - call(Pid, {recv_chunk_start, RemoteFile}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% recv_chunk(Pid, RemoteFile) -> ok | {ok, Bin} | {error, Reason} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Transfer file from remote server into binary in chunks -%%-------------------------------------------------------------------------- - --spec recv_chunk(Pid :: pid()) -> - 'ok' | - {'ok', Bin :: binary()} | - {'error', Reason :: restriction_reason() | common_reason()}. - -recv_chunk(Pid) -> - call(Pid, recv_chunk, atom). - - -%%-------------------------------------------------------------------------- -%% send(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} -%% | {error, elogin} -%% | {error, econn} -%% Pid = pid() -%% LocalFileName = RemotFileName = string() -%% -%% Description: Transfer file to remote server. -%%-------------------------------------------------------------------------- - --spec send(Pid :: pid(), LocalFileName :: string()) -> - 'ok' | - {'error', Reason :: restriction_reason() | - common_reason() | - shortage_reason()}. - -send(Pid, LocalFileName) -> - send(Pid, LocalFileName, LocalFileName). - --spec send(Pid :: pid(), - LocalFileName :: string(), - RemoteFileName :: string()) -> - 'ok' | - {'error', Reason :: restriction_reason() | - common_reason() | - shortage_reason()}. - -send(Pid, LocalFileName, RemotFileName) -> - case is_name_sane(RemotFileName) of - true -> - call(Pid, {send, LocalFileName, RemotFileName}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% send_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} -%% | {error, enotbinary} | {error, econn} -%% Pid = pid() -%% Bin = binary() -%% RemoteFile = string() -%% -%% Description: Transfer a binary to a remote file. -%%-------------------------------------------------------------------------- - --spec send_bin(Pid :: pid(), Bin :: binary(), RemoteFile :: string()) -> - 'ok' | - {'error', Reason :: restriction_reason() | - common_reason() | - shortage_reason()}. - -send_bin(Pid, Bin, RemoteFile) when is_binary(Bin) -> - case is_name_sane(RemoteFile) of - true -> - call(Pid, {send_bin, Bin, RemoteFile}, atom); - _ -> - {error, efnamena} - end; -send_bin(_Pid, _Bin, _RemoteFile) -> - {error, enotbinary}. - - -%%-------------------------------------------------------------------------- -%% send_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | {error, epath} -%% | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Start transfer of chunks to remote file. -%%-------------------------------------------------------------------------- - --spec send_chunk_start(Pid :: pid(), RemoteFile :: string()) -> - 'ok' | {'error', Reason :: restriction_reason() | common_reason()}. - -send_chunk_start(Pid, RemoteFile) -> - case is_name_sane(RemoteFile) of - true -> - call(Pid, {send_chunk_start, RemoteFile}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% append_chunk_start(Pid, RemoteFile) -> ok | {error, elogin} | -%% {error, epath} | {error, econn} -%% Pid = pid() -%% RemoteFile = string() -%% -%% Description: Start append chunks of data to remote file. -%%-------------------------------------------------------------------------- - --spec append_chunk_start(Pid :: pid(), RemoteFile :: string()) -> - 'ok' | {'error', Reason :: term()}. - -append_chunk_start(Pid, RemoteFile) -> - case is_name_sane(RemoteFile) of - true -> - call(Pid, {append_chunk_start, RemoteFile}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% send_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} -%% | {error, echunk} | {error, econn} -%% Pid = pid() -%% Bin = binary(). -%% -%% Purpose: Send chunk to remote file. -%%-------------------------------------------------------------------------- - --spec send_chunk(Pid :: pid(), Bin :: binary()) -> - 'ok' | - {'error', Reason :: 'echunk' | - restriction_reason() | - common_reason()}. - -send_chunk(Pid, Bin) when is_binary(Bin) -> - call(Pid, {transfer_chunk, Bin}, atom); -send_chunk(_Pid, _Bin) -> - {error, enotbinary}. - - -%%-------------------------------------------------------------------------- -%% append_chunk(Pid, Bin) -> ok | {error, elogin} | {error, enotbinary} -%% | {error, echunk} | {error, econn} -%% Pid = pid() -%% Bin = binary() -%% -%% Description: Append chunk to remote file. -%%-------------------------------------------------------------------------- - --spec append_chunk(Pid :: pid(), Bin :: binary()) -> - 'ok' | - {'error', Reason :: 'echunk' | - restriction_reason() | - common_reason()}. - -append_chunk(Pid, Bin) when is_binary(Bin) -> - call(Pid, {transfer_chunk, Bin}, atom); -append_chunk(_Pid, _Bin) -> - {error, enotbinary}. - - -%%-------------------------------------------------------------------------- -%% send_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} -%% | {error, econn} -%% Pid = pid() -%% -%% Description: End sending of chunks to remote file. -%%-------------------------------------------------------------------------- - --spec send_chunk_end(Pid :: pid()) -> - 'ok' | - {'error', Reason :: restriction_reason() | - common_reason() | - shortage_reason()}. - -send_chunk_end(Pid) -> - call(Pid, chunk_end, atom). - - -%%-------------------------------------------------------------------------- -%% append_chunk_end(Pid) -> ok | {error, elogin} | {error, echunk} -%% | {error, econn} -%% Pid = pid() -%% -%% Description: End appending of chunks to remote file. -%%-------------------------------------------------------------------------- - --spec append_chunk_end(Pid :: pid()) -> - 'ok' | - {'error', Reason :: restriction_reason() | - common_reason() | - shortage_reason()}. - -append_chunk_end(Pid) -> - call(Pid, chunk_end, atom). - - -%%-------------------------------------------------------------------------- -%% append(Pid, LocalFileName [, RemotFileName]) -> ok | {error, epath} -%% | {error, elogin} -%% | {error, econn} -%% Pid = pid() -%% LocalFileName = RemotFileName = string() -%% -%% Description: Append the local file to the remote file -%%-------------------------------------------------------------------------- - --spec append(Pid :: pid(), LocalFileName :: string()) -> - 'ok' | - {'error', Reason :: 'epath' | - 'elogin' | - 'etnospc' | - 'epnospc' | - 'efnamena' | common_reason()}. - -append(Pid, LocalFileName) -> - append(Pid, LocalFileName, LocalFileName). - --spec append(Pid :: pid(), - LocalFileName :: string(), - RemoteFileName :: string()) -> - 'ok' | {'error', Reason :: term()}. - -append(Pid, LocalFileName, RemotFileName) -> - case is_name_sane(RemotFileName) of - true -> - call(Pid, {append, LocalFileName, RemotFileName}, atom); - _ -> - {error, efnamena} - end. - - -%%-------------------------------------------------------------------------- -%% append_bin(Pid, Bin, RemoteFile) -> ok | {error, epath} | {error, elogin} -%% | {error, enotbinary} | {error, econn} -%% Pid = pid() -%% Bin = binary() -%% RemoteFile = string() -%% -%% Purpose: Append a binary to a remote file. -%%-------------------------------------------------------------------------- - --spec append_bin(Pid :: pid(), - Bin :: binary(), - RemoteFile :: string()) -> - 'ok' | - {'error', Reason :: restriction_reason() | - common_reason() | - shortage_reason()}. - -append_bin(Pid, Bin, RemoteFile) when is_binary(Bin) -> - case is_name_sane(RemoteFile) of - true -> - call(Pid, {append_bin, Bin, RemoteFile}, atom); - _ -> - {error, efnamena} - end; -append_bin(_Pid, _Bin, _RemoteFile) -> - {error, enotbinary}. - - -%%-------------------------------------------------------------------------- -%% quote(Pid, Cmd) -> list() -%% Pid = pid() -%% Cmd = string() -%% -%% Description: Send arbitrary ftp command. -%%-------------------------------------------------------------------------- - --spec quote(Pid :: pid(), Cmd :: string()) -> list(). - -quote(Pid, Cmd) when is_list(Cmd) -> - call(Pid, {quote, Cmd}, atom). - - -%%-------------------------------------------------------------------------- -%% close(Pid) -> ok -%% Pid = pid() -%% -%% Description: End the ftp session. -%%-------------------------------------------------------------------------- - --spec close(Pid :: pid()) -> 'ok'. - -close(Pid) -> - cast(Pid, close), - ok. - - -%%-------------------------------------------------------------------------- -%% formaterror(Tag) -> string() -%% Tag = atom() | {error, atom()} -%% -%% Description: Return diagnostics. -%%-------------------------------------------------------------------------- - --spec formaterror(Tag :: term()) -> string(). - -formaterror(Tag) -> - ftp_response:error_string(Tag). - - -info(Pid) -> - call(Pid, info, list). - - -%%-------------------------------------------------------------------------- -%% latest_ctrl_response(Pid) -> string() -%% Pid = pid() -%% -%% Description: The latest received response from the server -%%-------------------------------------------------------------------------- - --spec latest_ctrl_response(Pid :: pid()) -> string(). - -latest_ctrl_response(Pid) -> - call(Pid, latest_ctrl_response, string). - -%%%======================================================================== -%%% Behavior callbacks -%%%======================================================================== -start_standalone(Options) -> - try - {ok, StartOptions} = start_options(Options), - {ok, OpenOptions} = open_options(Options), - case start_link(StartOptions, []) of - {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); - Error1 -> - Error1 - end - catch - throw:Error2 -> - Error2 - end. - -start_service(Options) -> - try - {ok, StartOptions} = start_options(Options), - {ok, OpenOptions} = open_options(Options), - case ftp_sup:start_child([[[{client, self()} | StartOptions], []]]) of - {ok, Pid} -> - call(Pid, {open, ip_comm, OpenOptions}, plain); - Error1 -> - Error1 - end - catch - throw:Error2 -> - Error2 - end. - -stop_service(Pid) -> - close(Pid). - -services() -> - [{ftpc, Pid} || {_, Pid, _, _} <- - supervisor:which_children(ftp_sup)]. -service_info(Pid) -> - {ok, Info} = call(Pid, info, list), - {ok, [proplists:lookup(mode, Info), - proplists:lookup(local_port, Info), - proplists:lookup(peer, Info), - proplists:lookup(peer_port, Info)]}. - - -%% This function extracts the start options from the -%% Valid options: -%% debug, -%% verbose -%% ipfamily -%% priority -%% flags (for backward compatibillity) -start_options(Options) -> - ?fcrt("start_options", [{options, Options}]), - case lists:keysearch(flags, 1, Options) of - {value, {flags, Flags}} -> - Verbose = lists:member(verbose, Flags), - IsTrace = lists:member(trace, Flags), - IsDebug = lists:member(debug, Flags), - DebugLevel = - if - (IsTrace =:= true) -> - trace; - IsDebug =:= true -> - debug; - true -> - disable - end, - {ok, [{verbose, Verbose}, - {debug, DebugLevel}, - {priority, low}]}; - false -> - ValidateVerbose = - fun(true) -> true; - (false) -> true; - (_) -> false - end, - ValidateDebug = - fun(trace) -> true; - (debug) -> true; - (disable) -> true; - (_) -> false - end, - ValidatePriority = - fun(low) -> true; - (normal) -> true; - (high) -> true; - (_) -> false - end, - ValidOptions = - [{verbose, ValidateVerbose, false, false}, - {debug, ValidateDebug, false, disable}, - {priority, ValidatePriority, false, low}], - validate_options(Options, ValidOptions, []) - end. - - -%% This function extracts and validates the open options from the -%% Valid options: -%% mode -%% host -%% port -%% timeout -%% dtimeout -%% progress -%% ftp_extension - -open_options(Options) -> - ?fcrt("open_options", [{options, Options}]), - ValidateMode = - fun(active) -> true; - (passive) -> true; - (_) -> false - end, - ValidateHost = - fun(Host) when is_list(Host) -> - true; - (Host) when is_tuple(Host) andalso - ((size(Host) =:= 4) orelse (size(Host) =:= 8)) -> - true; - (_) -> - false - end, - ValidatePort = - fun(Port) when is_integer(Port) andalso (Port > 0) -> true; - (_) -> false - end, - ValidateIpFamily = - fun(inet) -> true; - (inet6) -> true; - (inet6fb4) -> true; - (_) -> false - end, - ValidateTimeout = - fun(Timeout) when is_integer(Timeout) andalso (Timeout >= 0) -> true; - (_) -> false - end, - ValidateDTimeout = - fun(DTimeout) when is_integer(DTimeout) andalso (DTimeout >= 0) -> true; - (infinity) -> true; - (_) -> false - end, - ValidateProgress = - fun(ignore) -> - true; - ({Mod, Func, _InitProgress}) when is_atom(Mod) andalso - is_atom(Func) -> - true; - (_) -> - false - end, - ValidateFtpExtension = - fun(true) -> true; - (false) -> true; - (_) -> false - end, - ValidOptions = - [{mode, ValidateMode, false, ?DEFAULT_MODE}, - {host, ValidateHost, true, ehost}, - {port, ValidatePort, false, ?FTP_PORT}, - {ipfamily, ValidateIpFamily, false, inet}, - {timeout, ValidateTimeout, false, ?CONNECTION_TIMEOUT}, - {dtimeout, ValidateDTimeout, false, ?DATA_ACCEPT_TIMEOUT}, - {progress, ValidateProgress, false, ?PROGRESS_DEFAULT}, - {ftp_extension, ValidateFtpExtension, false, ?FTP_EXT_DEFAULT}], - validate_options(Options, ValidOptions, []). - -tls_options(Options) -> - %% Options will be validated by ssl application - proplists:get_value(tls, Options, undefined). - -validate_options([], [], Acc) -> - ?fcrt("validate_options -> done", [{acc, Acc}]), - {ok, lists:reverse(Acc)}; -validate_options([], ValidOptions, Acc) -> - ?fcrt("validate_options -> done", - [{valid_options, ValidOptions}, {acc, Acc}]), - %% Check if any mandatory options are missing! - case [{Key, Reason} || {Key, _, true, Reason} <- ValidOptions] of - [] -> - Defaults = - [{Key, Default} || {Key, _, _, Default} <- ValidOptions], - {ok, lists:reverse(Defaults ++ Acc)}; - [{_, Reason}|_Missing] -> - throw({error, Reason}) - end; -validate_options([{Key, Value}|Options], ValidOptions, Acc) -> - ?fcrt("validate_options -> check", - [{key, Key}, {value, Value}, {acc, Acc}]), - case lists:keysearch(Key, 1, ValidOptions) of - {value, {Key, Validate, _, Default}} -> - case (catch Validate(Value)) of - true -> - ?fcrt("validate_options -> check - accept", []), - NewValidOptions = lists:keydelete(Key, 1, ValidOptions), - validate_options(Options, NewValidOptions, - [{Key, Value} | Acc]); - _ -> - ?fcrt("validate_options -> check - reject", - [{default, Default}]), - NewValidOptions = lists:keydelete(Key, 1, ValidOptions), - validate_options(Options, NewValidOptions, - [{Key, Default} | Acc]) - end; - false -> - validate_options(Options, ValidOptions, Acc) - end; -validate_options([_|Options], ValidOptions, Acc) -> - validate_options(Options, ValidOptions, Acc). - - - -%%%======================================================================== -%%% gen_server callback functions -%%%======================================================================== - -%%------------------------------------------------------------------------- -%% init(Args) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} -%% Description: Initiates the erlang process that manages a ftp connection. -%%------------------------------------------------------------------------- -init(Options) -> - process_flag(trap_exit, true), - - %% Keep track of the client - {value, {client, Client}} = lists:keysearch(client, 1, Options), - erlang:monitor(process, Client), - - %% Make sure inet is started - _ = inet_db:start(), - - %% Where are we - {ok, Dir} = file:get_cwd(), - - %% Maybe activate dbg - case key_search(debug, Options, disable) of - trace -> - dbg:tracer(), - dbg:p(all, [call]), - {ok, _} = dbg:tpl(ftp, [{'_', [], [{return_trace}]}]), - {ok, _} = dbg:tpl(ftp_response, [{'_', [], [{return_trace}]}]), - {ok, _} = dbg:tpl(ftp_progress, [{'_', [], [{return_trace}]}]), - ok; - debug -> - dbg:tracer(), - dbg:p(all, [call]), - {ok, _} = dbg:tp(ftp, [{'_', [], [{return_trace}]}]), - {ok, _} = dbg:tp(ftp_response, [{'_', [], [{return_trace}]}]), - {ok, _} = dbg:tp(ftp_progress, [{'_', [], [{return_trace}]}]), - ok; - _ -> - %% Keep silent - ok - end, - - %% Verbose? - Verbose = key_search(verbose, Options, false), - - %% IpFamily? - IpFamily = key_search(ipfamily, Options, inet), - - State = #state{owner = Client, - verbose = Verbose, - ipfamily = IpFamily, - ldir = Dir}, - - %% Set process prio - Priority = key_search(priority, Options, low), - process_flag(priority, Priority), - - %% And we are done - {ok, State}. - - -%%-------------------------------------------------------------------------- -%% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% Description: Handle incoming requests. -%%------------------------------------------------------------------------- - -%% Anyone can ask this question -handle_call({_, info}, _, #state{verbose = Verbose, - mode = Mode, - timeout = Timeout, - ipfamily = IpFamily, - csock = Socket, - progress = Progress} = State) -> - {ok, {_, LocalPort}} = sockname(Socket), - {ok, {Address, Port}} = peername(Socket), - Options = [{verbose, Verbose}, - {ipfamily, IpFamily}, - {mode, Mode}, - {peer, Address}, - {peer_port, Port}, - {local_port, LocalPort}, - {timeout, Timeout}, - {progress, Progress}], - {reply, {ok, Options}, State}; - -handle_call({_,latest_ctrl_response}, _, #state{latest_ctrl_response=Resp} = State) -> - {reply, {ok,Resp}, State}; - -%% But everything else must come from the owner -handle_call({Pid, _}, _, #state{owner = Owner} = State) when Owner =/= Pid -> - {reply, {error, not_connection_owner}, State}; - -handle_call({_, {open, ip_comm, Opts}}, From, State) -> - ?fcrd("handle_call(open)", [{opts, Opts}]), - case key_search(host, Opts, undefined) of - undefined -> - {stop, normal, {error, ehost}, State}; - Host -> - Mode = key_search(mode, Opts, ?DEFAULT_MODE), - Port = key_search(port, Opts, ?FTP_PORT), - Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), - DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), - Progress = key_search(progress, Opts, ignore), - IpFamily = key_search(ipfamily, Opts, inet), - FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), - - State2 = State#state{client = From, - mode = Mode, - progress = progress(Progress), - ipfamily = IpFamily, - dtimeout = DTimeout, - ftp_extension = FtpExt}, - - ?fcrd("handle_call(open) -> setup ctrl connection with", - [{host, Host}, {port, Port}, {timeout, Timeout}]), - case setup_ctrl_connection(Host, Port, Timeout, State2) of - {ok, State3, WaitTimeout} -> - ?fcrd("handle_call(open) -> ctrl connection setup done", - [{waittimeout, WaitTimeout}]), - {noreply, State3, WaitTimeout}; - {error, Reason} -> - ?fcrd("handle_call(open) -> ctrl connection setup failed", - [{reason, Reason}]), - gen_server:reply(From, {error, ehost}), - {stop, normal, State2#state{client = undefined}} - end - end; - -handle_call({_, {open, ip_comm, Host, Opts}}, From, State) -> - Mode = key_search(mode, Opts, ?DEFAULT_MODE), - Port = key_search(port, Opts, ?FTP_PORT), - Timeout = key_search(timeout, Opts, ?CONNECTION_TIMEOUT), - DTimeout = key_search(dtimeout, Opts, ?DATA_ACCEPT_TIMEOUT), - Progress = key_search(progress, Opts, ignore), - FtpExt = key_search(ftp_extension, Opts, ?FTP_EXT_DEFAULT), - - State2 = State#state{client = From, - mode = Mode, - progress = progress(Progress), - dtimeout = DTimeout, - ftp_extension = FtpExt}, - - case setup_ctrl_connection(Host, Port, Timeout, State2) of - {ok, State3, WaitTimeout} -> - {noreply, State3, WaitTimeout}; - {error, _Reason} -> - gen_server:reply(From, {error, ehost}), - {stop, normal, State2#state{client = undefined}} - end; - -handle_call({_, {open, tls_upgrade, TLSOptions}}, From, State) -> - send_ctrl_message(State, mk_cmd("AUTH TLS", [])), - activate_ctrl_connection(State), - {noreply, State#state{client = From, caller = open, tls_options = TLSOptions}}; - -handle_call({_, {user, User, Password}}, From, - #state{csock = CSock} = State) when (CSock =/= undefined) -> - handle_user(User, Password, "", State#state{client = From}); - -handle_call({_, {user, User, Password, Acc}}, From, - #state{csock = CSock} = State) when (CSock =/= undefined) -> - handle_user(User, Password, Acc, State#state{client = From}); - -handle_call({_, {account, Acc}}, From, State)-> - handle_user_account(Acc, State#state{client = From}); - -handle_call({_, pwd}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("PWD", [])), - activate_ctrl_connection(State), - {noreply, State#state{client = From, caller = pwd}}; - -handle_call({_, lpwd}, From, #state{ldir = LDir} = State) -> - {reply, {ok, LDir}, State#state{client = From}}; - -handle_call({_, {cd, Dir}}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])), - activate_ctrl_connection(State), - {noreply, State#state{client = From, caller = cd}}; - -handle_call({_,{lcd, Dir}}, _From, #state{ldir = LDir0} = State) -> - LDir = filename:absname(Dir, LDir0), - case file:read_file_info(LDir) of %% FIX better check that LDir is a dir. - {ok, _ } -> - {reply, ok, State#state{ldir = LDir}}; - _ -> - {reply, {error, epath}, State} - end; - -handle_call({_, {dir, Len, Dir}}, {_Pid, _} = From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {dir, Dir, Len}, - client = From}); -handle_call({_, {rename, CurrFile, NewFile}}, From, - #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("RNFR ~s", [CurrFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {rename, NewFile}, client = From}}; - -handle_call({_, {delete, File}}, {_Pid, _} = From, - #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("DELE ~s", [File])), - activate_ctrl_connection(State), - {noreply, State#state{client = From}}; - -handle_call({_, {mkdir, Dir}}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("MKD ~s", [Dir])), - activate_ctrl_connection(State), - {noreply, State#state{client = From}}; - -handle_call({_,{rmdir, Dir}}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd("RMD ~s", [Dir])), - activate_ctrl_connection(State), - {noreply, State#state{client = From}}; - -handle_call({_,{type, Type}}, From, #state{chunk = false} = State) -> - case Type of - ascii -> - send_ctrl_message(State, mk_cmd("TYPE A", [])), - activate_ctrl_connection(State), - {noreply, State#state{caller = type, type = ascii, - client = From}}; - binary -> - send_ctrl_message(State, mk_cmd("TYPE I", [])), - activate_ctrl_connection(State), - {noreply, State#state{caller = type, type = binary, - client = From}}; - _ -> - {reply, {error, etype}, State} - end; - -handle_call({_,{recv, RemoteFile, LocalFile}}, From, - #state{chunk = false, ldir = LocalDir} = State) -> - progress_report({remote_file, RemoteFile}, State), - NewLocalFile = filename:absname(LocalFile, LocalDir), - - case file_open(NewLocalFile, write) of - {ok, Fd} -> - setup_data_connection(State#state{client = From, - caller = - {recv_file, - RemoteFile, Fd}}); - {error, _What} -> - {reply, {error, epath}, State} - end; - -handle_call({_, {recv_bin, RemoteFile}}, From, #state{chunk = false} = - State) -> - setup_data_connection(State#state{caller = {recv_bin, RemoteFile}, - client = From}); - -handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false} - = State) -> - setup_data_connection(State#state{caller = {start_chunk_transfer, - "RETR", RemoteFile}, - client = From}); - -handle_call({_, recv_chunk}, _, #state{chunk = false} = State) -> - {reply, {error, "ftp:recv_chunk_start/2 not called"}, State}; - -handle_call({_, recv_chunk}, _From, #state{chunk = true, - caller = #recv_chunk_closing{dconn_closed = true, - pos_compl_received = true - } - } = State0) -> - %% The ftp:recv_chunk call was the last event we waited for, finnish and clean up - ?DBG("recv_chunk_closing ftp:recv_chunk, last event",[]), - activate_ctrl_connection(State0), - {reply, ok, State0#state{caller = undefined, - chunk = false, - client = undefined}}; - -handle_call({_, recv_chunk}, From, #state{chunk = true, - caller = #recv_chunk_closing{} = R - } = State) -> - %% Waiting for more, don't care what - ?DBG("recv_chunk_closing ftp:recv_chunk, get more",[]), - {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}}; - -handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) -> - State = activate_data_connection(State0), - {noreply, State#state{client = From, caller = recv_chunk}}; - -handle_call({_, {send, LocalFile, RemoteFile}}, From, - #state{chunk = false, ldir = LocalDir} = State) -> - progress_report({local_file, filename:absname(LocalFile, LocalDir)}, - State), - setup_data_connection(State#state{caller = {transfer_file, - {"STOR", - LocalFile, RemoteFile}}, - client = From}); -handle_call({_, {append, LocalFile, RemoteFile}}, From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {transfer_file, - {"APPE", - LocalFile, RemoteFile}}, - client = From}); -handle_call({_, {send_bin, Bin, RemoteFile}}, From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {transfer_data, - {"STOR", Bin, RemoteFile}}, - client = From}); -handle_call({_,{append_bin, Bin, RemoteFile}}, From, - #state{chunk = false} = State) -> - setup_data_connection(State#state{caller = {transfer_data, - {"APPE", Bin, RemoteFile}}, - client = From}); -handle_call({_, {send_chunk_start, RemoteFile}}, From, #state{chunk = false} - = State) -> - setup_data_connection(State#state{caller = {start_chunk_transfer, - "STOR", RemoteFile}, - client = From}); -handle_call({_, {append_chunk_start, RemoteFile}}, From, #state{chunk = false} - = State) -> - setup_data_connection(State#state{caller = {start_chunk_transfer, - "APPE", RemoteFile}, - client = From}); -handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) -> - send_data_message(State, Bin), - {reply, ok, State}; - -handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) -> - {reply, {error, echunk}, State}; - -handle_call({_, chunk_end}, From, #state{chunk = true} = State) -> - close_data_connection(State), - activate_ctrl_connection(State), - {noreply, State#state{client = From, dsock = undefined, - caller = end_chunk_transfer, chunk = false}}; - -handle_call({_, chunk_end}, _, #state{chunk = false} = State) -> - {reply, {error, echunk}, State}; - -handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State) -> - send_ctrl_message(State, mk_cmd(Cmd, [])), - activate_ctrl_connection(State), - {noreply, State#state{client = From, caller = quote}}; - -handle_call({_, _Req}, _From, #state{csock = CSock} = State) - when (CSock =:= undefined) -> - {reply, {error, not_connected}, State}; - -handle_call(_, _, #state{chunk = true} = State) -> - {reply, {error, echunk}, State}; - -%% Catch all - This can only happen if the application programmer writes -%% really bad code that violates the API. -handle_call(Request, _Timeout, State) -> - {stop, {'API_violation_connection_closed', Request}, - {error, {connection_terminated, 'API_violation'}}, State}. - -%%-------------------------------------------------------------------------- -%% handle_cast(Request, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handles cast messages. -%%------------------------------------------------------------------------- -handle_cast({Pid, close}, #state{owner = Pid} = State) -> - send_ctrl_message(State, mk_cmd("QUIT", [])), - close_ctrl_connection(State), - close_data_connection(State), - {stop, normal, State#state{csock = undefined, dsock = undefined}}; - -handle_cast({Pid, close}, State) -> - Report = io_lib:format("A none owner process ~p tried to close an " - "ftp connection: ~n", [Pid]), - error_logger:info_report(Report), - {noreply, State}; - -%% Catch all - This can oly happen if the application programmer writes -%% really bad code that violates the API. -handle_cast(Msg, State) -> - {stop, {'API_violation_connection_closed', Msg}, State}. - -%%-------------------------------------------------------------------------- -%% handle_info(Msg, State) -> {noreply, State} | {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handles tcp messages from the ftp-server. -%% Note: The order of the function clauses is significant. -%%-------------------------------------------------------------------------- - -handle_info(timeout, #state{caller = open} = State) -> - {stop, timeout, State}; - -handle_info(timeout, State) -> - {noreply, State}; - -%%% Data socket messages %%% -handle_info({Trpt, Socket, Data}, - #state{dsock = {Trpt,Socket}, - caller = {recv_file, Fd}} = State0) when Trpt==tcp;Trpt==ssl -> - ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]), - ok = file_write(binary_to_list(Data), Fd), - progress_report({binary, Data}, State0), - State = activate_data_connection(State0), - {noreply, State}; - -handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}, client = From, - caller = recv_chunk} - = State) when Trpt==tcp;Trpt==ssl -> - ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State]), - gen_server:reply(From, {ok, Data}), - {noreply, State#state{client = undefined, data = <<>>}}; - -handle_info({Trpt, Socket, Data}, #state{dsock = {Trpt,Socket}} = State0) when Trpt==tcp;Trpt==ssl -> - ?DBG('L~p --data ~p ----> ~s~p~n',[?LINE,Socket,Data,State0]), - State = activate_data_connection(State0), - {noreply, State#state{data = <<(State#state.data)/binary, - Data/binary>>}}; - -handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, - caller = {recv_file, Fd}} = State) - when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> - file_close(Fd), - progress_report({transfer_size, 0}, State), - activate_ctrl_connection(State), - ?DBG("Data channel close",[]), - {noreply, State#state{dsock = undefined, data = <<>>}}; - -handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, - client = Client, - caller = recv_chunk} = State) - when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> - ?DBG("Data channel close recv_chunk",[]), - activate_ctrl_connection(State), - {noreply, State#state{dsock = undefined, - caller = #recv_chunk_closing{dconn_closed = true, - client_called_us = Client =/= undefined} - }}; - -handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin, - data = Data} = State) - when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> - ?DBG("Data channel close",[]), - activate_ctrl_connection(State), - {noreply, State#state{dsock = undefined, data = <<>>, - caller = {recv_bin, Data}}}; - -handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data, - caller = {handle_dir_result, Dir}} - = State) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> - ?DBG("Data channel close",[]), - activate_ctrl_connection(State), - {noreply, State#state{dsock = undefined, - caller = {handle_dir_result, Dir, Data}, -% data = <>}}; - data = <<>>}}; - -handle_info({Err, Socket, Reason}, #state{dsock = {Trpt,Socket}, - client = From} = State) - when {Err,Trpt}=={tcp_error,tcp} ; {Err,Trpt}=={ssl_error,ssl} -> - gen_server:reply(From, {error, Reason}), - close_data_connection(State), - {noreply, State#state{dsock = undefined, client = undefined, - data = <<>>, caller = undefined, chunk = false}}; - -%%% Ctrl socket messages %%% -handle_info({Transport, Socket, Data}, #state{csock = {Transport, Socket}, - verbose = Verbose, - caller = Caller, - client = From, - ctrl_data = {CtrlData, AccLines, - LineStatus}} - = State) -> - ?DBG('--ctrl ~p ----> ~s~p~n',[Socket,<>,State]), - case ftp_response:parse_lines(<>, - AccLines, LineStatus) of - {ok, Lines, NextMsgData} -> - verbose(Lines, Verbose, 'receive'), - CtrlResult = ftp_response:interpret(Lines), - case Caller of - quote -> - gen_server:reply(From, string:tokens(Lines, [?CR, ?LF])), - {noreply, State#state{client = undefined, - caller = undefined, - latest_ctrl_response = Lines, - ctrl_data = {NextMsgData, [], - start}}}; - _ -> - ?DBG(' ...handle_ctrl_result(~p,...) ctrl_data=~p~n',[CtrlResult,{NextMsgData, [], start}]), - handle_ctrl_result(CtrlResult, - State#state{latest_ctrl_response = Lines, - ctrl_data = - {NextMsgData, [], start}}) - end; - {continue, NewCtrlData} -> - ?DBG(' ...Continue... ctrl_data=~p~n',[NewCtrlData]), - activate_ctrl_connection(State), - {noreply, State#state{ctrl_data = NewCtrlData}} - end; - -%% If the server closes the control channel it is -%% the expected behavior that connection process terminates. -handle_info({Cls, Socket}, #state{csock = {Trpt, Socket}}) - when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} -> - exit(normal); %% User will get error message from terminate/2 - -handle_info({Err, Socket, Reason}, _) when Err==tcp_error ; Err==ssl_error -> - Report = - io_lib:format("~p on socket: ~p for reason: ~p~n", - [Err, Socket, Reason]), - error_logger:error_report(Report), - %% If tcp does not work the only option is to terminate, - %% this is the expected behavior under these circumstances. - exit(normal); %% User will get error message from terminate/2 - -%% Monitor messages - if the process owning the ftp connection goes -%% down there is no point in continuing. -handle_info({'DOWN', _Ref, _Type, _Process, normal}, State) -> - {stop, normal, State#state{client = undefined}}; - -handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) -> - {stop, normal, State#state{client = undefined}}; - -handle_info({'DOWN', _Ref, _Type, _Process, timeout}, State) -> - {stop, normal, State#state{client = undefined}}; - -handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) -> - {stop, {stopped, {'EXIT', Process, Reason}}, - State#state{client = undefined}}; - -handle_info({'EXIT', Pid, Reason}, #state{progress = Pid} = State) -> - Report = io_lib:format("Progress reporting stopped for reason ~p~n", - [Reason]), - error_logger:info_report(Report), - {noreply, State#state{progress = ignore}}; - -%% Catch all - throws away unknown messages (This could happen by "accident" -%% so we do not want to crash, but we make a log entry as it is an -%% unwanted behaviour.) -handle_info(Info, State) -> - Report = io_lib:format("ftp : ~p : Unexpected message: ~p~nState: ~p~n", - [self(), Info, State]), - error_logger:info_report(Report), - {noreply, State}. - -%%-------------------------------------------------------------------------- -%% terminate/2 and code_change/3 -%%-------------------------------------------------------------------------- -terminate(normal, State) -> - %% If terminate reason =/= normal the progress reporting process will - %% be killed by the exit signal. - progress_report(stop, State), - do_terminate({error, econn}, State); -terminate(Reason, State) -> - Report = io_lib:format("Ftp connection closed due to: ~p~n", [Reason]), - error_logger:error_report(Report), - do_terminate({error, eclosed}, State). - -do_terminate(ErrorMsg, State) -> - close_data_connection(State), - close_ctrl_connection(State), - case State#state.client of - undefined -> - ok; - From -> - gen_server:reply(From, ErrorMsg) - end, - ok. - -code_change(_Vsn, State1, upgrade_from_pre_5_12) -> - {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, - Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress} = State1, - IpFamily = - if - (IPv6Disable =:= true) -> - inet; - true -> - inet6fb4 - end, - State2 = #state{csock = CSock, - dsock = DSock, - verbose = Verbose, - ldir = LDir, - type = Type, - chunk = Chunk, - mode = Mode, - timeout = Timeout, - data = Data, - ctrl_data = CtrlData, - owner = Owner, - client = Client, - caller = Caller, - ipfamily = IpFamily, - progress = Progress}, - {ok, State2}; - -code_change(_Vsn, State1, downgrade_to_pre_5_12) -> - #state{csock = CSock, - dsock = DSock, - verbose = Verbose, - ldir = LDir, - type = Type, - chunk = Chunk, - mode = Mode, - timeout = Timeout, - data = Data, - ctrl_data = CtrlData, - owner = Owner, - client = Client, - caller = Caller, - ipfamily = IpFamily, - progress = Progress} = State1, - IPv6Disable = - if - (IpFamily =:= inet) -> - true; - true -> - false - end, - State2 = - {state, CSock, DSock, Verbose, LDir, Type, Chunk, Mode, Timeout, - Data, CtrlData, Owner, Client, Caller, IPv6Disable, Progress}, - {ok, State2}; - -code_change(_Vsn, State, _Extra) -> - {ok, State}. - - -%%%========================================================================= -%% Start/stop -%%%========================================================================= -%%-------------------------------------------------------------------------- -%% start_link([Opts, GenServerOptions]) -> {ok, Pid} | {error, Reason} -%% -%% Description: Callback function for the ftp supervisor. It is called -%% : when start_service/1 calls ftp_sup:start_child/1 to start an -%% : instance of the ftp process. Also called by start_standalone/1 -%%-------------------------------------------------------------------------- -start_link([Opts, GenServerOptions]) -> - start_link(Opts, GenServerOptions). - -start_link(Opts, GenServerOptions) -> - case lists:keysearch(client, 1, Opts) of - {value, _} -> - %% Via the supervisor - gen_server:start_link(?MODULE, Opts, GenServerOptions); - false -> - Opts2 = [{client, self()} | Opts], - gen_server:start_link(?MODULE, Opts2, GenServerOptions) - end. - - -%%% Stop functionality is handled by close/1 - -%%%======================================================================== -%%% Internal functions -%%%======================================================================== - -%%-------------------------------------------------------------------------- -%%% Help functions to handle_call and/or handle_ctrl_result -%%-------------------------------------------------------------------------- -%% User handling -handle_user(User, Password, Acc, State) -> - send_ctrl_message(State, mk_cmd("USER ~s", [User])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {handle_user, Password, Acc}}}. - -handle_user_passwd(Password, Acc, State) -> - send_ctrl_message(State, mk_cmd("PASS ~s", [Password])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {handle_user_passwd, Acc}}}. - -handle_user_account(Acc, State) -> - send_ctrl_message(State, mk_cmd("ACCT ~s", [Acc])), - activate_ctrl_connection(State), - {noreply, State#state{caller = handle_user_account}}. - - -%%-------------------------------------------------------------------------- -%% handle_ctrl_result -%%-------------------------------------------------------------------------- -handle_ctrl_result({tls_upgrade, _}, #state{csock = {tcp, Socket}, - tls_options = TLSOptions, - timeout = Timeout, - caller = open, client = From} - = State0) -> - ?DBG('<--ctrl ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]), - case ssl:connect(Socket, TLSOptions, Timeout) of - {ok, TLSSocket} -> - State = State0#state{csock = {ssl,TLSSocket}}, - send_ctrl_message(State, mk_cmd("PBSZ 0", [])), - activate_ctrl_connection(State), - {noreply, State#state{tls_upgrading_data_connection = {true, pbsz}} }; - {error, _} = Error -> - gen_server:reply(From, {Error, self()}), - {stop, normal, State0#state{client = undefined, - caller = undefined, - tls_upgrading_data_connection = false}} - end; - -handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, pbsz}} = State) -> - send_ctrl_message(State, mk_cmd("PROT P", [])), - activate_ctrl_connection(State), - {noreply, State#state{tls_upgrading_data_connection = {true, prot}}}; - -handle_ctrl_result({pos_compl, _}, #state{tls_upgrading_data_connection = {true, prot}, - client = From} = State) -> - gen_server:reply(From, {ok, self()}), - {noreply, State#state{client = undefined, - caller = undefined, - tls_upgrading_data_connection = false}}; - -handle_ctrl_result({pos_compl, _}, #state{caller = open, client = From} - = State) -> - gen_server:reply(From, {ok, self()}), - {noreply, State#state{client = undefined, - caller = undefined }}; -handle_ctrl_result({_, Lines}, #state{caller = open} = State) -> - ctrl_result_response(econn, State, {error, Lines}); - -%%-------------------------------------------------------------------------- -%% Data connection setup active mode -handle_ctrl_result({pos_compl, _Lines}, - #state{mode = active, - caller = {setup_data_connection, - {LSock, Caller}}} = State) -> - handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}}); - -handle_ctrl_result({Status, _Lines}, - #state{mode = active, - caller = {setup_data_connection, {LSock, _}}} - = State) -> - close_connection({tcp,LSock}), - ctrl_result_response(Status, State, {error, Status}); - -%% Data connection setup passive mode -handle_ctrl_result({pos_compl, Lines}, - #state{mode = passive, - ipfamily = inet6, - client = From, - caller = {setup_data_connection, Caller}, - csock = CSock, - timeout = Timeout} - = State) -> - [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), - {ok, {IP, _}} = peername(CSock), - case connect(IP, list_to_integer(PortStr), Timeout, State) of - {ok, _, Socket} -> - handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); - {error, _Reason} = Error -> - gen_server:reply(From, Error), - {noreply, State#state{client = undefined, caller = undefined}} - end; - -handle_ctrl_result({pos_compl, Lines}, - #state{mode = passive, - ipfamily = inet, - client = From, - caller = {setup_data_connection, Caller}, - timeout = Timeout, - ftp_extension = false} = State) -> - - {_, [?LEFT_PAREN | Rest]} = - lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines), - {NewPortAddr, _} = - lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest), - [A1, A2, A3, A4, P1, P2] = - lists:map(fun(X) -> list_to_integer(X) end, - string:tokens(NewPortAddr, [$,])), - IP = {A1, A2, A3, A4}, - Port = (P1 * 256) + P2, - - ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]), - case connect(IP, Port, Timeout, State) of - {ok, _, Socket} -> - handle_caller(State#state{caller = Caller, dsock = {tcp,Socket}}); - {error, _Reason} = Error -> - gen_server:reply(From, Error), - {noreply,State#state{client = undefined, caller = undefined}} - end; - -handle_ctrl_result({pos_compl, Lines}, - #state{mode = passive, - ipfamily = inet, - client = From, - caller = {setup_data_connection, Caller}, - csock = CSock, - timeout = Timeout, - ftp_extension = true} = State) -> - - [_, PortStr | _] = lists:reverse(string:tokens(Lines, "|")), - {ok, {IP, _}} = peername(CSock), - - ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,PortStr,Caller]), - case connect(IP, list_to_integer(PortStr), Timeout, State) of - {ok, _, Socket} -> - handle_caller(State#state{caller = Caller, dsock = {tcp, Socket}}); - {error, _Reason} = Error -> - gen_server:reply(From, Error), - {noreply, State#state{client = undefined, caller = undefined}} - end; - - -%% FTP server does not support passive mode: try to fallback on active mode -handle_ctrl_result(_, - #state{mode = passive, - caller = {setup_data_connection, Caller}} = State) -> - setup_data_connection(State#state{mode = active, caller = Caller}); - - -%%-------------------------------------------------------------------------- -%% User handling -handle_ctrl_result({pos_interm, _}, - #state{caller = {handle_user, PassWord, Acc}} = State) -> - handle_user_passwd(PassWord, Acc, State); -handle_ctrl_result({Status, _}, - #state{caller = {handle_user, _, _}} = State) -> - ctrl_result_response(Status, State, {error, euser}); - -%% Accounts -handle_ctrl_result({pos_interm_acct, _}, - #state{caller = {handle_user_passwd, Acc}} = State) - when Acc =/= "" -> - handle_user_account(Acc, State); -handle_ctrl_result({Status, _}, - #state{caller = {handle_user_passwd, _}} = State) -> - ctrl_result_response(Status, State, {error, euser}); - -%%-------------------------------------------------------------------------- -%% Print current working directory -handle_ctrl_result({pos_compl, Lines}, - #state{caller = pwd, client = From} = State) -> - Dir = pwd_result(Lines), - gen_server:reply(From, {ok, Dir}), - {noreply, State#state{client = undefined, caller = undefined}}; - -%%-------------------------------------------------------------------------- -%% Directory listing -handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) -> - case accept_data_connection(State0) of - {ok, State1} -> - State = activate_data_connection(State1), - {noreply, State#state{caller = {handle_dir_result, Dir}}}; - {error, _Reason} = ERROR -> - case State0#state.client of - undefined -> - {stop, ERROR, State0}; - From -> - gen_server:reply(From, ERROR), - {stop, normal, State0#state{client = undefined}} - end - end; - -handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir, - Data}, client = From} - = State) -> - case Dir of - "" -> % Current directory - gen_server:reply(From, {ok, Data}), - {noreply, State#state{client = undefined, - caller = undefined}}; - _ -> - %% - %% Dir cannot be assumed to be a dir. It is a string that - %% could be a dir, but could also be a file or even a string - %% containing wildcards (*). - %% - %% %% If there is only one line it might be a directory with one - %% %% file but it might be an error message that the directory - %% %% was not found. So in this case we have to endure a little - %% %% overhead to be able to give a good return value. Alas not - %% %% all ftp implementations behave the same and returning - %% %% an error string is allowed by the FTP RFC. - %% case lists:dropwhile(fun(?CR) -> false;(_) -> true end, - %% binary_to_list(Data)) of - %% L when (L =:= [?CR, ?LF]) orelse (L =:= []) -> - %% send_ctrl_message(State, mk_cmd("PWD", [])), - %% activate_ctrl_connection(State), - %% {noreply, - %% State#state{caller = {handle_dir_data, Dir, Data}}}; - %% _ -> - %% gen_server:reply(From, {ok, Data}), - %% {noreply, State#state{client = undefined, - %% caller = undefined}} - %% end - %% - gen_server:reply(From, {ok, Data}), - {noreply, State#state{client = undefined, - caller = undefined}} - end; - -handle_ctrl_result({pos_compl, Lines}, - #state{caller = {handle_dir_data, Dir, DirData}} = - State) -> - OldDir = pwd_result(Lines), - send_ctrl_message(State, mk_cmd("CWD ~s", [Dir])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {handle_dir_data_second_phase, OldDir, - DirData}}}; -handle_ctrl_result({Status, _}, - #state{caller = {handle_dir_data, _, _}} = State) -> - ctrl_result_response(Status, State, {error, epath}); - -handle_ctrl_result(S={_Status, _}, - #state{caller = {handle_dir_result, _, _}} = State) -> - %% OTP-5731, macosx - ctrl_result_response(S, State, {error, epath}); - -handle_ctrl_result({pos_compl, _}, - #state{caller = {handle_dir_data_second_phase, OldDir, - DirData}} = State) -> - send_ctrl_message(State, mk_cmd("CWD ~s", [OldDir])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {handle_dir_data_third_phase, DirData}}}; -handle_ctrl_result({Status, _}, - #state{caller = {handle_dir_data_second_phase, _, _}} - = State) -> - ctrl_result_response(Status, State, {error, epath}); -handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData}, - client = From} = State) -> - gen_server:reply(From, {ok, DirData}), - {noreply, State#state{client = undefined, caller = undefined}}; - -handle_ctrl_result({Status, _}, #state{caller = cd} = State) -> - ctrl_result_response(Status, State, {error, Status}); - -handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) -> - ctrl_result_response(Status, State, {error, epath}); - -%%-------------------------------------------------------------------------- -%% File renaming -handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}} - = State) -> - send_ctrl_message(State, mk_cmd("RNTO ~s", [NewFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = rename_second_phase}}; - -handle_ctrl_result({Status, _}, - #state{caller = {rename, _}} = State) -> - ctrl_result_response(Status, State, {error, Status}); - -handle_ctrl_result({Status, _}, - #state{caller = rename_second_phase} = State) -> - ctrl_result_response(Status, State, {error, Status}); - -%%-------------------------------------------------------------------------- -%% File handling - recv_bin -handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) -> - case accept_data_connection(State0) of - {ok, State1} -> - State = activate_data_connection(State1), - {noreply, State}; - {error, _Reason} = ERROR -> - case State0#state.client of - undefined -> - {stop, ERROR, State0}; - From -> - gen_server:reply(From, ERROR), - {stop, normal, State0#state{client = undefined}} - end - end; - -handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data}, - client = From} = State) -> - gen_server:reply(From, {ok, Data}), - close_data_connection(State), - {noreply, State#state{client = undefined, caller = undefined}}; - -handle_ctrl_result({Status, _}, #state{caller = recv_bin} = State) -> - close_data_connection(State), - ctrl_result_response(Status, State#state{dsock = undefined}, - {error, epath}); - -handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) -> - close_data_connection(State), - ctrl_result_response(Status, State#state{dsock = undefined}, - {error, epath}); -%%-------------------------------------------------------------------------- -%% File handling - start_chunk_transfer -handle_ctrl_result({pos_prel, _}, #state{client = From, - caller = start_chunk_transfer} - = State0) -> - case accept_data_connection(State0) of - {ok, State1} -> - State = start_chunk(State1), - {noreply, State}; - {error, _Reason} = ERROR -> - case State0#state.client of - undefined -> - {stop, ERROR, State0}; - From -> - gen_server:reply(From, ERROR), - {stop, normal, State0#state{client = undefined}} - end - end; - -%%-------------------------------------------------------------------------- -%% File handling - chunk_transfer complete - -handle_ctrl_result({pos_compl, _}, #state{client = From, - caller = #recv_chunk_closing{dconn_closed = true, - client_called_us = true, - pos_compl_received = false - }} - = State0) when From =/= undefined -> - %% The pos_compl was the last event we waited for, finnish and clean up - ?DBG("recv_chunk_closing pos_compl, last event",[]), - gen_server:reply(From, ok), - activate_ctrl_connection(State0), - {noreply, State0#state{caller = undefined, - chunk = false, - client = undefined}}; - -handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R} - = State0) -> - %% Waiting for more, don't care what - ?DBG("recv_chunk_closing pos_compl, wait more",[]), - {noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}}; - - -%%-------------------------------------------------------------------------- -%% File handling - recv_file -handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) -> - case accept_data_connection(State0) of - {ok, State1} -> - State = activate_data_connection(State1), - {noreply, State}; - {error, _Reason} = ERROR -> - case State0#state.client of - undefined -> - {stop, ERROR, State0}; - From -> - gen_server:reply(From, ERROR), - {stop, normal, State0#state{client = undefined}} - end - end; - -handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) -> - file_close(Fd), - close_data_connection(State), - ctrl_result_response(Status, State#state{dsock = undefined}, - {error, epath}); -%%-------------------------------------------------------------------------- -%% File handling - transfer_* -handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}} - = State0) -> - case accept_data_connection(State0) of - {ok, State1} -> - send_file(State1, Fd); - {error, _Reason} = ERROR -> - case State0#state.client of - undefined -> - {stop, ERROR, State0}; - From -> - gen_server:reply(From, ERROR), - {stop, normal, State0#state{client = undefined}} - end - end; - -handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} - = State0) -> - case accept_data_connection(State0) of - {ok, State} -> - send_bin(State, Bin); - {error, _Reason} = ERROR -> - case State0#state.client of - undefined -> - {stop, ERROR, State0}; - From -> - gen_server:reply(From, ERROR), - {stop, normal, State0#state{client = undefined}} - end - end; - -%%-------------------------------------------------------------------------- -%% Default -handle_ctrl_result({Status, _Lines}, #state{client = From} = State) - when From =/= undefined -> - ctrl_result_response(Status, State, {error, Status}). - -%%-------------------------------------------------------------------------- -%% Help functions to handle_ctrl_result -%%-------------------------------------------------------------------------- -ctrl_result_response(pos_compl, #state{client = From} = State, _) -> - gen_server:reply(From, ok), - {noreply, State#state{client = undefined, caller = undefined}}; - -ctrl_result_response(enofile, #state{client = From} = State, _) -> - gen_server:reply(From, {error, enofile}), - {noreply, State#state{client = undefined, caller = undefined}}; - -ctrl_result_response(Status, #state{client = From} = State, _) - when (Status =:= etnospc) orelse - (Status =:= epnospc) orelse - (Status =:= efnamena) orelse - (Status =:= econn) -> - gen_server:reply(From, {error, Status}), -%% {stop, normal, {error, Status}, State#state{client = undefined}}; - {stop, normal, State#state{client = undefined}}; - -ctrl_result_response(_, #state{client = From} = State, ErrorMsg) -> - gen_server:reply(From, ErrorMsg), - {noreply, State#state{client = undefined, caller = undefined}}. - -%%-------------------------------------------------------------------------- -handle_caller(#state{caller = {dir, Dir, Len}} = State) -> - Cmd = case Len of - short -> "NLST"; - long -> "LIST" - end, - case Dir of - "" -> - send_ctrl_message(State, mk_cmd(Cmd, "")); - _ -> - send_ctrl_message(State, mk_cmd(Cmd ++ " ~s", [Dir])) - end, - activate_ctrl_connection(State), - {noreply, State#state{caller = {dir, Dir}}}; - -handle_caller(#state{caller = {recv_bin, RemoteFile}} = State) -> - send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = recv_bin}}; - -handle_caller(#state{caller = {start_chunk_transfer, Cmd, RemoteFile}} = - State) -> - send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = start_chunk_transfer}}; - -handle_caller(#state{caller = {recv_file, RemoteFile, Fd}} = State) -> - send_ctrl_message(State, mk_cmd("RETR ~s", [RemoteFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {recv_file, Fd}}}; - -handle_caller(#state{caller = {transfer_file, {Cmd, LocalFile, RemoteFile}}, - ldir = LocalDir, client = From} = State) -> - case file_open(filename:absname(LocalFile, LocalDir), read) of - {ok, Fd} -> - send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {transfer_file, Fd}}}; - {error, _} -> - gen_server:reply(From, {error, epath}), - {noreply, State#state{client = undefined, caller = undefined, - dsock = undefined}} - end; - -handle_caller(#state{caller = {transfer_data, {Cmd, Bin, RemoteFile}}} = - State) -> - send_ctrl_message(State, mk_cmd("~s ~s", [Cmd, RemoteFile])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {transfer_data, Bin}}}. - -%% ----------- FTP SERVER COMMUNICATION ------------------------- - -%% Connect to FTP server at Host (default is TCP port 21) -%% in order to establish a control connection. -setup_ctrl_connection(Host, Port, Timeout, State) -> - MsTime = erlang:monotonic_time(), - case connect(Host, Port, Timeout, State) of - {ok, IpFam, CSock} -> - NewState = State#state{csock = {tcp, CSock}, ipfamily = IpFam}, - activate_ctrl_connection(NewState), - case Timeout - inets_lib:millisec_passed(MsTime) of - Timeout2 when (Timeout2 >= 0) -> - {ok, NewState#state{caller = open}, Timeout2}; - _ -> - %% Oups: Simulate timeout - {ok, NewState#state{caller = open}, 0} - end; - Error -> - Error - end. - -setup_data_connection(#state{mode = active, - caller = Caller, - csock = CSock, - ftp_extension = FtpExt} = State) -> - case (catch sockname(CSock)) of - {ok, {{_, _, _, _, _, _, _, _} = IP, _}} -> - {ok, LSock} = - gen_tcp:listen(0, [{ip, IP}, {active, false}, - inet6, binary, {packet, 0}]), - {ok, {_, Port}} = sockname({tcp,LSock}), - IpAddress = inet_parse:ntoa(IP), - Cmd = mk_cmd("EPRT |2|~s|~p|", [IpAddress, Port]), - send_ctrl_message(State, Cmd), - activate_ctrl_connection(State), - {noreply, State#state{caller = {setup_data_connection, - {LSock, Caller}}}}; - {ok, {{_,_,_,_} = IP, _}} -> - {ok, LSock} = gen_tcp:listen(0, [{ip, IP}, {active, false}, - binary, {packet, 0}]), - {ok, Port} = inet:port(LSock), - case FtpExt of - false -> - {IP1, IP2, IP3, IP4} = IP, - {Port1, Port2} = {Port div 256, Port rem 256}, - send_ctrl_message(State, - mk_cmd("PORT ~w,~w,~w,~w,~w,~w", - [IP1, IP2, IP3, IP4, Port1, Port2])); - true -> - IpAddress = inet_parse:ntoa(IP), - Cmd = mk_cmd("EPRT |1|~s|~p|", [IpAddress, Port]), - send_ctrl_message(State, Cmd) - end, - activate_ctrl_connection(State), - {noreply, State#state{caller = {setup_data_connection, - {LSock, Caller}}}} - end; - -setup_data_connection(#state{mode = passive, ipfamily = inet6, - caller = Caller} = State) -> - send_ctrl_message(State, mk_cmd("EPSV", [])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {setup_data_connection, Caller}}}; - -setup_data_connection(#state{mode = passive, ipfamily = inet, - caller = Caller, - ftp_extension = false} = State) -> - send_ctrl_message(State, mk_cmd("PASV", [])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {setup_data_connection, Caller}}}; - -setup_data_connection(#state{mode = passive, ipfamily = inet, - caller = Caller, - ftp_extension = true} = State) -> - send_ctrl_message(State, mk_cmd("EPSV", [])), - activate_ctrl_connection(State), - {noreply, State#state{caller = {setup_data_connection, Caller}}}. - -connect(Host, Port, Timeout, #state{ipfamily = inet = IpFam}) -> - connect2(Host, Port, IpFam, Timeout); - -connect(Host, Port, Timeout, #state{ipfamily = inet6 = IpFam}) -> - connect2(Host, Port, IpFam, Timeout); - -connect(Host, Port, Timeout, #state{ipfamily = inet6fb4}) -> - case inet:getaddr(Host, inet6) of - {ok, {0, 0, 0, 0, 0, 16#ffff, _, _} = IPv6} -> - case inet:getaddr(Host, inet) of - {ok, IPv4} -> - IpFam = inet, - connect2(IPv4, Port, IpFam, Timeout); - - _ -> - IpFam = inet6, - connect2(IPv6, Port, IpFam, Timeout) - end; - - {ok, IPv6} -> - IpFam = inet6, - connect2(IPv6, Port, IpFam, Timeout); - - _ -> - case inet:getaddr(Host, inet) of - {ok, IPv4} -> - IpFam = inet, - connect2(IPv4, Port, IpFam, Timeout); - Error -> - Error - end - end. - -connect2(Host, Port, IpFam, Timeout) -> - Opts = [IpFam, binary, {packet, 0}, {active, false}], - case gen_tcp:connect(Host, Port, Opts, Timeout) of - {ok, Sock} -> - {ok, IpFam, Sock}; - Error -> - Error - end. - - -accept_data_connection(#state{mode = active, - dtimeout = DTimeout, - tls_options = TLSOptions, - dsock = {lsock, LSock}} = State0) -> - case gen_tcp:accept(LSock, DTimeout) of - {ok, Socket} when is_list(TLSOptions) -> - gen_tcp:close(LSock), - ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State0]), - case ssl:connect(Socket, TLSOptions, DTimeout) of - {ok, TLSSocket} -> - {ok, State0#state{dsock={ssl,TLSSocket}}}; - {error, Reason} -> - {error, {ssl_connect_failed, Reason}} - end; - {ok, Socket} -> - gen_tcp:close(LSock), - {ok, State0#state{dsock={tcp,Socket}}}; - {error, Reason} -> - {error, {data_connect_failed, Reason}} - end; - -accept_data_connection(#state{mode = passive, - dtimeout = DTimeout, - dsock = {tcp,Socket}, - tls_options = TLSOptions} = State) when is_list(TLSOptions) -> - ?DBG('<--data ssl:connect(~p, ~p)~n~p~n',[Socket,TLSOptions,State]), - case ssl:connect(Socket, TLSOptions, DTimeout) of - {ok, TLSSocket} -> - {ok, State#state{dsock={ssl,TLSSocket}}}; - {error, Reason} -> - {error, {ssl_connect_failed, Reason}} - end; -accept_data_connection(#state{mode = passive} = State) -> - {ok,State}. - - -send_ctrl_message(_S=#state{csock = Socket, verbose = Verbose}, Message) -> - verbose(lists:flatten(Message),Verbose,send), - ?DBG('<--ctrl ~p ---- ~s~p~n',[Socket,Message,_S]), - _ = send_message(Socket, Message). - -send_data_message(_S=#state{dsock = Socket}, Message) -> - ?DBG('<==data ~p ==== ~s~n~p~n',[Socket,Message,_S]), - case send_message(Socket, Message) of - ok -> - ok; - {error, Reason} -> - Report = io_lib:format("send/2 for socket ~p failed with " - "reason ~p~n", [Socket, Reason]), - error_logger:error_report(Report), - %% If tcp/ssl does not work the only option is to terminate, - %% this is the expected behavior under these circumstances. - exit(normal) %% User will get error message from terminate/2 - end. - -send_message({tcp, Socket}, Message) -> - gen_tcp:send(Socket, Message); -send_message({ssl, Socket}, Message) -> - ssl:send(Socket, Message). - -activate_ctrl_connection(#state{csock = CSock, ctrl_data = {<<>>, _, _}}) -> - activate_connection(CSock); -activate_ctrl_connection(#state{csock = CSock}) -> - activate_connection(CSock), - %% We have already received at least part of the next control message, - %% that has been saved in ctrl_data, process this first. - self() ! {socket_type(CSock), unwrap_socket(CSock), <<>>}, - ok. - -activate_data_connection(#state{dsock = DSock} = State) -> - activate_connection(DSock), - State. - -activate_connection(Socket) -> - ignore_return_value( - case socket_type(Socket) of - tcp -> inet:setopts(unwrap_socket(Socket), [{active, once}]); - ssl -> ssl:setopts(unwrap_socket(Socket), [{active, once}]) - end). - - -ignore_return_value(_) -> ok. - -unwrap_socket({tcp,Socket}) -> Socket; -unwrap_socket({ssl,Socket}) -> Socket. - -socket_type({tcp,_Socket}) -> tcp; -socket_type({ssl,_Socket}) -> ssl. - -close_ctrl_connection(#state{csock = undefined}) -> ok; -close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket). - -close_data_connection(#state{dsock = undefined}) -> ok; -close_data_connection(#state{dsock = Socket}) -> close_connection(Socket). - -close_connection({lsock,Socket}) -> ignore_return_value( gen_tcp:close(Socket) ); -close_connection({tcp, Socket}) -> ignore_return_value( gen_tcp:close(Socket) ); -close_connection({ssl, Socket}) -> ignore_return_value( ssl:close(Socket) ). - -%% ------------ FILE HANDLING ---------------------------------------- -send_file(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Fd) -> - {noreply, State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_file, Fd}}}; -send_file(State, Fd) -> - case file_read(Fd) of - {ok, N, Bin} when N > 0 -> - send_data_message(State, Bin), - progress_report({binary, Bin}, State), - send_file(State, Fd); - {ok, _, _} -> - file_close(Fd), - close_data_connection(State), - progress_report({transfer_size, 0}, State), - activate_ctrl_connection(State), - {noreply, State#state{caller = transfer_file_second_phase, - dsock = undefined}}; - {error, Reason} -> - gen_server:reply(State#state.client, {error, Reason}), - {stop, normal, State#state{client = undefined}} - end. - -file_open(File, Option) -> - file:open(File, [raw, binary, Option]). - -file_close(Fd) -> - ignore_return_value( file:close(Fd) ). - -file_read(Fd) -> - case file:read(Fd, ?FILE_BUFSIZE) of - {ok, Bytes} -> - {ok, size(Bytes), Bytes}; - eof -> - {ok, 0, []}; - Other -> - Other - end. - -file_write(Bytes, Fd) -> - file:write(Fd, Bytes). - -%% -------------- MISC ---------------------------------------------- - -call(GenServer, Msg, Format) -> - call(GenServer, Msg, Format, infinity). -call(GenServer, Msg, Format, Timeout) -> - Req = {self(), Msg}, - case (catch gen_server:call(GenServer, Req, Timeout)) of - {ok, Bin} when is_binary(Bin) andalso (Format =:= string) -> - {ok, binary_to_list(Bin)}; - {'EXIT', _} -> - {error, eclosed}; - Result -> - Result - end. - -cast(GenServer, Msg) -> - gen_server:cast(GenServer, {self(), Msg}). - -send_bin(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State, Bin) -> - State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, send_bin, Bin}}; -send_bin(State, Bin) -> - send_data_message(State, Bin), - close_data_connection(State), - activate_ctrl_connection(State), - {noreply, State#state{caller = transfer_data_second_phase, - dsock = undefined}}. - -mk_cmd(Fmt, Args) -> - [io_lib:format(Fmt, Args)| [?CR, ?LF]]. % Deep list ok. - -is_name_sane([]) -> - true; -is_name_sane([?CR| _]) -> - false; -is_name_sane([?LF| _]) -> - false; -is_name_sane([_| Rest]) -> - is_name_sane(Rest). - -pwd_result(Lines) -> - {_, [?DOUBLE_QUOTE | Rest]} = - lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Lines), - {Dir, _} = - lists:splitwith(fun(?DOUBLE_QUOTE) -> false; (_) -> true end, Rest), - Dir. - - -key_search(Key, List, Default) -> - case lists:keysearch(Key, 1, List) of - {value, {_,Val}} -> - Val; - false -> - Default - end. - -verbose(Lines, true, Direction) -> - DirStr = - case Direction of - send -> - "Sending: "; - _ -> - "Receiving: " - end, - Str = string:strip(string:strip(Lines, right, ?LF), right, ?CR), - erlang:display(DirStr++Str); -verbose(_, false,_) -> - ok. - -progress(Options) -> - ftp_progress:start_link(Options). - -progress_report(_, #state{progress = ignore}) -> - ok; -progress_report(stop, #state{progress = ProgressPid}) -> - ftp_progress:stop(ProgressPid); -progress_report({binary, Data}, #state{progress = ProgressPid}) -> - ftp_progress:report(ProgressPid, {transfer_size, size(Data)}); -progress_report(Report, #state{progress = ProgressPid}) -> - ftp_progress:report(ProgressPid, Report). - - -peername({tcp, Socket}) -> inet:peername(Socket); -peername({ssl, Socket}) -> ssl:peername(Socket). - -sockname({tcp, Socket}) -> inet:sockname(Socket); -sockname({ssl, Socket}) -> ssl:sockname(Socket). - -maybe_tls_upgrade(Pid, undefined) -> - {ok, Pid}; -maybe_tls_upgrade(Pid, TLSOptions) -> - catch ssl:start(), - call(Pid, {open, tls_upgrade, TLSOptions}, plain). - -start_chunk(#state{tls_upgrading_data_connection = {true, CTRL, _}} = State) -> - State#state{tls_upgrading_data_connection = {true, CTRL, ?MODULE, start_chunk, undefined}}; -start_chunk(#state{client = From} = State) -> - gen_server:reply(From, ok), - State#state{chunk = true, - client = undefined, - caller = undefined}. diff --git a/lib/inets/src/ftp/ftp_internal.hrl b/lib/inets/src/ftp/ftp_internal.hrl deleted file mode 100644 index f29bb4a099..0000000000 --- a/lib/inets/src/ftp/ftp_internal.hrl +++ /dev/null @@ -1,33 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% - --ifndef(ftp_internal_hrl). --define(ftp_internal_hrl, true). - --include_lib("inets/src/inets_app/inets_internal.hrl"). - --define(SERVICE, ftpc). --define(fcri(Label, Content), ?report_important(Label, ?SERVICE, Content)). --define(fcrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)). --define(fcrd(Label, Content), ?report_debug(Label, ?SERVICE, Content)). --define(fcrt(Label, Content), ?report_trace(Label, ?SERVICE, Content)). - --endif. % -ifdef(ftp_internal_hrl). diff --git a/lib/inets/src/ftp/ftp_progress.erl b/lib/inets/src/ftp/ftp_progress.erl deleted file mode 100644 index a6263e5cd7..0000000000 --- a/lib/inets/src/ftp/ftp_progress.erl +++ /dev/null @@ -1,136 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% -%% Description: This module impements a temporary process that -%% performes progress reporting during file transfer calling a user -%% defined callback function. Its life span is as long as the ftp connection -%% processes that spawned it lives. The purpose of this process is to -%% shild the ftp connection process from errors and time consuming operations -%% in the user defined callback function. - --module(ftp_progress). - -%% Internal API --export([start_link/1, report/2, stop/1]). - -%% Spawn export --export([init/1]). - --include_lib("kernel/include/file.hrl"). - --record(progress, { - file :: string() | 'undefined', - cb_module :: module(), - cb_function :: atom(), - init_progress_term :: term(), - current_progress_term :: term() - }). - -%%%========================================================================= -%%% Internal application API -%%%========================================================================= -%%-------------------------------------------------------------------------- -%% start_link(Options) -> ignore | pid() -%% Options = ignore | {CBModule, CBFunction, InitProgressTerm} -%% -%% Description: Starts the progress report process unless progress reporting -%% should not be performed. -%%-------------------------------------------------------------------------- --type options() :: 'ignore' | {module(), atom(), term()}. --spec start_link(options()) -> 'ignore' | pid(). -start_link(ignore) -> - ignore; -start_link(Options) -> - spawn_link(?MODULE, init, [Options]). - -%%-------------------------------------------------------------------------- -%% report_progress(Pid, Report) -> ok -%% Pid = pid() -%% Report = {local_file, File} | {remote_file, File} | -%% {transfer_size, Size} -%% Size = integer() -%% -%% Description: Reports progress to the reporting process that calls the -%% user defined callback function. -%%-------------------------------------------------------------------------- --type report() :: {'local_file', string()} | {'remote_file', string()} - | {'transfer_size', non_neg_integer()}. --spec report(pid(), report()) -> 'ok'. -report(Pid, Report) -> - Pid ! {progress_report, Report}, - ok. - -%%-------------------------------------------------------------------------- -%% stop(Pid) -> ok -%% Pid = pid() -%% -%% Description: -%%-------------------------------------------------------------------------- --spec stop(pid()) -> 'ok'. -stop(Pid) -> - Pid ! stop, - ok. - -%%%========================================================================= -%%% Internal functions -%%%========================================================================= -init(Options) -> - loop(progress(Options)). - -loop(Progress) -> - receive - {progress_report, Report} -> - NewProgress = report_progress(Report, Progress), - loop(NewProgress); - stop -> - ok - end. - -progress({CBModule, CBFunction, InitProgressTerm}) when is_atom(CBModule), - is_atom(CBFunction) -> - #progress{cb_module = CBModule, - cb_function = CBFunction, - init_progress_term = InitProgressTerm, - current_progress_term = InitProgressTerm}. - -report_progress({local_file, File}, Progress) -> - {ok, FileInfo} = file:read_file_info(File), - report_progress({file_size, FileInfo#file_info.size}, - Progress#progress{file = File}); - -report_progress({remote_file, File}, Progress) -> - report_progress({file_size, unknown}, Progress#progress{file = File}); - -report_progress(Size, #progress{file = File, - cb_module = CBModule, - cb_function = CBFunction, - current_progress_term = Term, - init_progress_term = InitTerm} = Progress) -> - - NewProgressTerm = CBModule:CBFunction(Term, File, Size), - - case Size of - {transfer_size, 0} -> - %% Transfer is compleat reset initial values - Progress#progress{current_progress_term = InitTerm, - file = undefined}; - _ -> - Progress#progress{current_progress_term = NewProgressTerm} - end. diff --git a/lib/inets/src/ftp/ftp_response.erl b/lib/inets/src/ftp/ftp_response.erl deleted file mode 100644 index d54d97dc91..0000000000 --- a/lib/inets/src/ftp/ftp_response.erl +++ /dev/null @@ -1,203 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% -%% Description: This module impements handling of ftp server responses. - --module(ftp_response). - -%% Internal API --export([parse_lines/3, interpret/1, error_string/1]). - --include("ftp_internal.hrl"). - -%% First group of reply code digits --define(POS_PREL, 1). --define(POS_COMPL, 2). --define(POS_INTERM, 3). --define(TRANS_NEG_COMPL, 4). --define(PERM_NEG_COMPL, 5). -%% Second group of reply code digits --define(SYNTAX,0). --define(INFORMATION,1). --define(CONNECTION,2). --define(AUTH_ACC,3). --define(UNSPEC,4). --define(FILE_SYSTEM,5). - -%%%========================================================================= -%%% INTERNAL API -%%%========================================================================= - -%%-------------------------------------------------------------------------- -%% parse_lines(Data, AccLines, StatusCode) -> {ok, Lines} | -%% {continue, {Data, -%% AccLines, StatusCode}} -%% -%% Data = binary() - data recived on the control connection from the -%% ftp-server. -%% AccLines = [string()] -%% StatusCode = start | {byte(), byte(), byte()} | finish - -%% Indicates where in the parsing process we are. -%% start - (looking for the status code of the message) -%% {byte(), byte(), byte()} - status code found, now -%% looking for the last line indication. -%% finish - now on the last line. -%% Description: Parses a ftp control response message. -%% "A reply is defined to contain the 3-digit code, followed by Space -%% , followed by one line of text (where some maximum line length -%% has been specified), and terminated by the Telnet end-of-line -%% code (CRLF), or a so called multilined reply for example: -%% -%% 123-First line -%% Second line -%% 234 A line beginning with numbers -%% 123 The last line -%% -%% The user-process then simply needs to search for the second -%% occurrence of the same reply code, followed by (Space), at -%% the beginning of a line, and ignore all intermediary lines. If -%% an intermediary line begins with a 3-digit number, the Server -%% will pad the front to avoid confusion. -%%-------------------------------------------------------------------------- - -%% Make sure we received the first 4 bytes so we know how to parse -%% the FTP server response e.i. is the response composed of one -%% or multiple lines. -parse_lines(Bin, Lines, start) when size(Bin) < 4 -> - {continue, {Bin, Lines, start}}; -%% Multiple lines exist -parse_lines(<>, Lines, start) -> - parse_lines(Rest, [$-, C3, C2, C1 | Lines], {C1, C2, C3}); -%% Only one line exists -parse_lines(<>, Lines, start) -> - parse_lines(Bin, [?WHITE_SPACE, C3, C2, C1 | Lines], finish); - -%% Last line found -parse_lines(<>, Lines, {C1, C2, C3}) -> - parse_lines(Rest, [?WHITE_SPACE, C3, C2, C1, ?LF, ?CR | Lines], finish); -%% Potential end found wait for more data -parse_lines(<> = Bin, Lines, {C1, C2, C3}) -> - {continue, {Bin, Lines, {C1, C2, C3}}}; -%% Intermidate line begining with status code -parse_lines(<>, Lines, {C1, C2, C3}) -> - parse_lines(Rest, [C3, C2, C1, ?LF, ?CR | Lines], {C1, C2, C3}); - -%% Potential last line wait for more data -parse_lines(<> = Data, Lines, {C1, C2, _} = StatusCode) -> - {continue, {Data, Lines, StatusCode}}; -parse_lines(<> = Data, Lines, {C1, _, _} = StatusCode) -> - {continue, {Data, Lines, StatusCode}}; -parse_lines(<> = Data, Lines, {_,_,_} = StatusCode) -> - {continue, {Data, Lines, StatusCode}}; -parse_lines(<> = Data, Lines, {_,_,_} = StatusCode) -> - {continue, {Data, Lines, StatusCode}}; -parse_lines(<<>> = Data, Lines, {_,_,_} = StatusCode) -> - {continue, {Data, Lines, StatusCode}}; -%% Part of the multiple lines -parse_lines(<>, Lines, {_,_, _} = StatusCode) -> - parse_lines(Rest, [Octet | Lines], StatusCode); - -%% End of FTP server response found -parse_lines(<>, Lines, finish) -> - {ok, lists:reverse([?LF, ?CR | Lines]), <<>>}; -parse_lines(<>, Lines, finish) -> - {ok, lists:reverse([?LF, ?CR | Lines]), Rest}; - -%% Potential end found wait for more data -parse_lines(<> = Data, Lines, finish) -> - {continue, {Data, Lines, finish}}; -parse_lines(<<>> = Data, Lines, finish) -> - {continue, {Data, Lines, finish}}; -%% Part of last line -parse_lines(<>, Lines, finish) -> - parse_lines(Rest, [Octet | Lines], finish). - -%%-------------------------------------------------------------------------- -%% interpret(Lines) -> {Status, Text} -%% Lines = [byte(), byte(), byte() | Text] - ftp server response as -%% returned by parse_lines/3 -%% Stauts = atom() (see interpret_status/3) -%% Text = [string()] -%% -%% Description: Create nicer data to match on. -%%-------------------------------------------------------------------------- -interpret([Didgit1, Didgit2, Didgit3 | Data]) -> - Code1 = Didgit1 - $0, - Code2 = Didgit2 - $0, - Code3 = Didgit3 - $0, - {interpret_status(Code1, Code2, Code3), Data}. - -%%-------------------------------------------------------------------------- -%% error_string(Error) -> string() -%% Error = {error, term()} | term() -%% -%% Description: Translates error codes into strings intended for -%% human interpretation. -%%-------------------------------------------------------------------------- -error_string({error, Reason}) -> - error_string(Reason); - -error_string(echunk) -> "Synchronisation error during chunk sending."; -error_string(eclosed) -> "Session has been closed."; -error_string(econn) -> "Connection to remote server prematurely closed."; -error_string(eexists) ->"File or directory already exists."; -error_string(ehost) -> "Host not found, FTP server not found, " - "or connection rejected."; -error_string(elogin) -> "User not logged in."; -error_string(enotbinary) -> "Term is not a binary."; -error_string(epath) -> "No such file or directory, already exists, " - "or permission denied."; -error_string(etype) -> "No such type."; -error_string(euser) -> "User name or password not valid."; -error_string(etnospc) -> "Insufficient storage space in system."; -error_string(enofile) -> "No files found or file unavailable"; -error_string(epnospc) -> "Exceeded storage allocation " - "(for current directory or dataset)."; -error_string(efnamena) -> "File name not allowed."; -error_string(Reason) -> - lists:flatten(io_lib:format("Unknown error: ~w", [Reason])). - -%%%======================================================================== -%%% Internal functions -%%%======================================================================== - -%% Positive Preleminary Reply -interpret_status(?POS_PREL,_,_) -> pos_prel; -%%FIXME ??? 3??? interpret_status(?POS_COMPL, ?AUTH_ACC, 3) -> tls_upgrade; -interpret_status(?POS_COMPL, ?AUTH_ACC, 4) -> tls_upgrade; -%% Positive Completion Reply -interpret_status(?POS_COMPL,_,_) -> pos_compl; -%% Positive Intermediate Reply nedd account -interpret_status(?POS_INTERM,?AUTH_ACC,2) -> pos_interm_acct; -%% Positive Intermediate Reply -interpret_status(?POS_INTERM,_,_) -> pos_interm; -%% No files found or file not available -interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,0) -> enofile; -%% No storage area no action taken -interpret_status(?TRANS_NEG_COMPL,?FILE_SYSTEM,2) -> etnospc; -%% Temporary Error, no action taken -interpret_status(?TRANS_NEG_COMPL,_,_) -> trans_neg_compl; -%% Permanent disk space error, the user shall not try again -interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,0) -> epath; -interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,2) -> epnospc; -interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,3) -> efnamena; -interpret_status(?PERM_NEG_COMPL,?AUTH_ACC,0) -> elogin; -interpret_status(?PERM_NEG_COMPL,_,_) -> perm_neg_compl. - diff --git a/lib/inets/src/ftp/ftp_sup.erl b/lib/inets/src/ftp/ftp_sup.erl deleted file mode 100644 index 21dcfb6ab2..0000000000 --- a/lib/inets/src/ftp/ftp_sup.erl +++ /dev/null @@ -1,60 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% -%%---------------------------------------------------------------------- -%% Purpose: The top supervisor for the ftp hangs under inets_sup. -%%---------------------------------------------------------------------- --module(ftp_sup). - --behaviour(supervisor). - -%% API --export([start_link/0]). --export([start_child/1]). - -%% Supervisor callback --export([init/1]). - -%%%========================================================================= -%%% API -%%%========================================================================= -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -start_child(Args) -> - supervisor:start_child(?MODULE, Args). - -%%%========================================================================= -%%% Supervisor callback -%%%========================================================================= -init(_) -> - RestartStrategy = simple_one_for_one, - MaxR = 0, - MaxT = 3600, - - Name = undefined, % As simple_one_for_one is used. - StartFunc = {ftp, start_link, []}, - Restart = temporary, % E.g. should not be restarted - Shutdown = 4000, - Modules = [ftp], - Type = worker, - - ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, - {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. diff --git a/lib/inets/src/inets_app/Makefile b/lib/inets/src/inets_app/Makefile index eb0098dbee..450e49dcc3 100644 --- a/lib/inets/src/inets_app/Makefile +++ b/lib/inets/src/inets_app/Makefile @@ -48,7 +48,8 @@ MODULES = \ inets_app \ inets_sup \ inets_trace \ - inets_lib + inets_lib \ + inets_ftp_wrapper INTERNAL_HRL_FILES = inets_internal.hrl EXTERNAL_HRL_FILES = ../../include/httpd.hrl \ diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src index eb4be932ac..0af44e39ab 100644 --- a/lib/inets/src/inets_app/inets.app.src +++ b/lib/inets/src/inets_app/inets.app.src @@ -30,10 +30,7 @@ inets_lib, %% FTP - ftp, - ftp_progress, - ftp_response, - ftp_sup, + inets_ftp_wrapper, %% HTTP client: httpc, diff --git a/lib/inets/src/inets_app/inets.erl b/lib/inets/src/inets_app/inets.erl index 2d380012d7..615a4cf635 100644 --- a/lib/inets/src/inets_app/inets.erl +++ b/lib/inets/src/inets_app/inets.erl @@ -470,8 +470,10 @@ service_module(tftpd) -> tftp; service_module(tftpc) -> tftp; +%% Obsolete! Kept for backward compatiblity! +%% FTP application has been moved out from inets service_module(ftpc) -> - ftp; + inets_ftp_wrapper; service_module(Service) -> Service. diff --git a/lib/inets/src/inets_app/inets_ftp_wrapper.erl b/lib/inets/src/inets_app/inets_ftp_wrapper.erl new file mode 100644 index 0000000000..b3fc7a8436 --- /dev/null +++ b/lib/inets/src/inets_app/inets_ftp_wrapper.erl @@ -0,0 +1,47 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(inets_ftp_wrapper). + + +-export([start_standalone/1, + start_service/1, + stop_service/1, + services/0, + service_info/1]). + + +start_standalone(_) -> + ok. + +start_service(Options) -> + application:ensure_started(ftp), + ftp:start_service(Options). + + +stop_service(Pid) -> + ftp:stop_service(Pid). + + +services() -> + []. + + +service_info(_) -> + []. diff --git a/lib/inets/src/inets_app/inets_sup.erl b/lib/inets/src/inets_app/inets_sup.erl index d8ae7eff26..8f4d806529 100644 --- a/lib/inets/src/inets_app/inets_sup.erl +++ b/lib/inets/src/inets_app/inets_sup.erl @@ -62,19 +62,9 @@ children() -> HttpdServices = [Service || Service <- Services, is_httpd(Service)], HttpcServices = [Service || Service <- Services, is_httpc(Service)], TftpdServices = [Service || Service <- Services, is_tftpd(Service)], - [ftp_child_spec(), httpc_child_spec(HttpcServices), + [httpc_child_spec(HttpcServices), httpd_child_spec(HttpdServices), tftpd_child_spec(TftpdServices)]. -ftp_child_spec() -> - Name = ftp_sup, - StartFunc = {ftp_sup, start_link, []}, - Restart = permanent, - Shutdown = infinity, - Modules = [ftp_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - - httpc_child_spec(HttpcServices0) -> HttpcServices = default_profile(HttpcServices0, []), Name = httpc_sup, diff --git a/lib/inets/src/subdirs.mk b/lib/inets/src/subdirs.mk index 9f2a0079f2..8c18691aef 100644 --- a/lib/inets/src/subdirs.mk +++ b/lib/inets/src/subdirs.mk @@ -1,3 +1,3 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SUB_DIRECTORIES = inets_app http_lib http_client http_server ftp tftp +SUB_DIRECTORIES = inets_app http_lib http_client http_server tftp diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile index 99a7e6a9db..4dab4e14c6 100644 --- a/lib/inets/test/Makefile +++ b/lib/inets/test/Makefile @@ -44,8 +44,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) INCLUDES = -I. \ -I$(ERL_TOP)/lib/inets/src/inets_app \ -I$(ERL_TOP)/lib/inets/src/http_lib \ - -I$(ERL_TOP)/lib/inets/src/http_client \ - -I$(ERL_TOP)/lib/inets/src/ftp + -I$(ERL_TOP)/lib/inets/src/http_client CP = cp @@ -68,34 +67,6 @@ INETS_FLAGS = -Dinets_data_dir='"$(INETS_DATA_DIR)"' \ ### ### test suite debug flags ### -ifeq ($(FTP_DEBUG_CLIENT),) - FTP_DEBUG_CLIENT = y -endif - -ifeq ($(FTP_DEBUG_CLIENT),) - FTP_FLAGS += -Dftp_debug_client -endif - -ifeq ($(FTP_TRACE_CLIENT),) - FTP_DEBUG_CLIENT = y -endif - -ifeq ($(FTP_TRACE_CLIENT),y) - FTP_FLAGS += -Dftp_trace_client -endif - -ifneq ($(FTP_DEBUG),) - FTP_DEBUG = s -endif - -ifeq ($(FTP_DEBUG),l) - FTP_FLAGS += -Dftp_log -endif - -ifeq ($(FTP_DEBUG),d) - FTP_FLAGS += -Dftp_debug -Dftp_log -endif - ifeq ($(INETS_DEBUG),) INETS_DEBUG = d endif @@ -151,8 +122,6 @@ MODULES = \ inets_test_lib \ erl_make_certs \ make_certs \ - ftp_SUITE \ - ftp_format_SUITE \ http_format_SUITE \ httpc_SUITE \ httpc_cookie_SUITE \ @@ -179,7 +148,6 @@ EBIN = . HRL_FILES = inets_test_lib.hrl \ inets_internal.hrl \ - ftp_internal.hrl \ httpc_internal.hrl \ http_internal.hrl \ tftp_test_lib.hrl @@ -198,7 +166,6 @@ INETS_FILES = inets.config $(INETS_SPECS) # inets_sup_suite \ # inets_httpd_suite \ # inets_httpc_suite \ -# inets_ftp_suite \ # inets_tftp_suite @@ -206,9 +173,8 @@ INETS_DATADIRS = inets_SUITE_data inets_socketwrap_SUITE_data HTTPD_DATADIRS = httpd_test_data httpd_SUITE_data httpd_basic_SUITE_data old_httpd_SUITE_data httpd_bench_SUITE_data HTTPC_DATADIRS = httpc_SUITE_data httpc_proxy_SUITE_data -FTP_DATADIRS = ftp_SUITE_data -DATADIRS = $(INETS_DATADIRS) $(HTTPD_DATADIRS) $(HTTPC_DATADIRS) $(FTP_DATADIRS) +DATADIRS = $(INETS_DATADIRS) $(HTTPD_DATADIRS) $(HTTPC_DATADIRS) EMAKEFILE = Emakefile MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile) @@ -238,7 +204,6 @@ RELTESTSYSBINDIR = $(RELTESTSYSALLDATADIR)/bin # ---------------------------------------------------- ERL_COMPILE_FLAGS += \ $(INCLUDES) \ - $(FTP_FLAGS) \ $(INETS_FLAGS) # ---------------------------------------------------- @@ -334,7 +299,6 @@ info: @echo "INETS_PRIV_DIR = $(INETS_PRIV_DIR)" @echo "INETS_ROOT = $(INETS_ROOT)" @echo "INETS_FLAGS = $(INETS_FLAGS)" - @echo "FTP_FLAGS = $(FTP_FLAGS)" tftp: erlc $(ERL_COMPILE_FLAGS) tftp_test_lib.erl tftp_SUITE.erl && erl -pa ../../inets/ebin -s tftp_SUITE t -s erlang halt diff --git a/lib/inets/test/ftp_SUITE.erl b/lib/inets/test/ftp_SUITE.erl deleted file mode 100644 index 3dfec01ba2..0000000000 --- a/lib/inets/test/ftp_SUITE.erl +++ /dev/null @@ -1,1180 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2017. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% - -%% -%% ct:run("../inets_test", ftp_SUITE). -%% - --module(ftp_SUITE). - --include_lib("kernel/include/file.hrl"). --include_lib("common_test/include/ct.hrl"). --include("inets_test_lib.hrl"). - -%% Note: This directive should only be used in test suites. --compile(export_all). - --define(FTP_USER, "anonymous"). --define(FTP_PASS(Cmnt), (fun({ok,__H}) -> "ftp_SUITE_"++Cmnt++"@" ++ __H; - (_) -> "ftp_SUITE_"++Cmnt++"@localhost" - end)(inet:gethostname()) - ). - --define(BAD_HOST, "badhostname"). --define(BAD_USER, "baduser"). --define(BAD_DIR, "baddirectory"). - --record(progress, { - current = 0, - total - }). - -%%-------------------------------------------------------------------- -%% Common Test interface functions ----------------------------------- -%%-------------------------------------------------------------------- -suite() -> - [{timetrap,{seconds,20}}]. - -all() -> - [ - {group, ftp_passive}, - {group, ftp_active}, - {group, ftps_passive}, - {group, ftps_active}, - error_ehost, - clean_shutdown - ]. - -groups() -> - [ - {ftp_passive, [], ftp_tests()}, - {ftp_active, [], ftp_tests()}, - {ftps_passive, [], ftp_tests()}, - {ftps_active, [], ftp_tests()} - ]. - -ftp_tests()-> - [ - user, - bad_user, - pwd, - cd, - lcd, - ls, - nlist, - rename, - delete, - mkdir, - rmdir, - send, - send_3, - send_bin, - send_chunk, - append, - append_bin, - append_chunk, - recv, - recv_3, - recv_bin, - recv_bin_twice, - recv_chunk, - recv_chunk_twice, - recv_chunk_three_times, - type, - quote, - error_elogin, - progress_report_send, - progress_report_recv, - not_owner, - unexpected_call, - unexpected_cast, - unexpected_bang - ]. - -%%-------------------------------------------------------------------- - -%%% Config -%%% key meaning -%%% ................................................................ -%%% ftpservers list of servers to check if they are available -%%% The element is: -%%% {Name, % string(). The os command name -%%% Path, % string(). The os PATH syntax, e.g "/bin:/usr/bin" -%%% StartCommand, % fun()->{ok,start_result()} | {error,string()}. -%%% % The command to start the daemon with. -%%% ChkUp, % fun(start_result()) -> string(). Os command to check -%%% % if the server is running. [] if not running. -%%% % The string in string() is suitable for logging. -%%% StopCommand, % fun(start_result()) -> void(). The command to stop the daemon with. -%%% AugmentFun, % fun(config()) -> config() Adds two funs for transforming names of files -%%% % and directories to the form they are returned from this server -%%% ServerHost, % string(). Mostly "localhost" -%%% ServerPort % pos_integer() -%%% } -%%% - --define(default_ftp_servers, - [{"vsftpd", - "/sbin:/usr/sbin:/usr/local/sbin", - fun(__CONF__, AbsName) -> - DataDir = proplists:get_value(data_dir,__CONF__), - ConfFile = filename:join(DataDir, "vsftpd.conf"), - PrivDir = proplists:get_value(priv_dir,__CONF__), - AnonRoot = PrivDir, - Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"), - " -oftpd_banner=erlang_otp_testing", - " -oanon_root=\"",AnonRoot,"\"", - " -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"", - " -orsa_private_key_file=\"",filename:join(DataDir,"server-key.pem"),"\"" - ], - Result = os:cmd(Cmd), - ct:log("Config file:~n~s~n~nServer start command:~n ~s~nResult:~n ~p", - [case file:read_file(ConfFile) of - {ok,X} -> X; - _ -> "" - end, - Cmd, Result - ]), - case Result of - [] -> {ok,'dont care'}; - [Msg] -> {error,Msg} - end - end, - fun(_StartResult) -> os:cmd("ps ax | grep erlang_otp_testing | grep -v grep") - end, - fun(_StartResult) -> os:cmd("kill `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`") - end, - fun(__CONF__) -> - AnonRoot = proplists:get_value(priv_dir,__CONF__), - [{id2ftp, fun(Id) -> filename:join(AnonRoot,Id) end}, - {id2ftp_result,fun(Id) -> filename:join(AnonRoot,Id) end} | __CONF__] - end, - "localhost", - 9999 - } - ] - ). - - -init_per_suite(Config) -> - case find_executable(Config) of - false -> - {skip, "No ftp server found"}; - {ok,Data} -> - TstDir = filename:join(proplists:get_value(priv_dir,Config), "test"), - file:make_dir(TstDir), - make_cert_files(dsa, rsa, "server-", proplists:get_value(data_dir,Config)), - start_ftpd([{test_dir,TstDir}, - {ftpd_data,Data} - | Config]) - end. - -end_per_suite(Config) -> - ps_ftpd(Config), - stop_ftpd(Config), - ps_ftpd(Config), - ok. - -%%-------------------------------------------------------------------- -init_per_group(Group, Config) when Group == ftps_active, - Group == ftps_passive -> - catch crypto:stop(), - try crypto:start() of - ok -> - Config - catch - _:_ -> - {skip, "Crypto did not start"} - end; - -init_per_group(_Group, Config) -> - Config. - -end_per_group(_Group, Config) -> - Config. - -%%-------------------------------------------------------------------- -init_per_testcase(Case, Config0) -> - Group = proplists:get_value(name, proplists:get_value(tc_group_properties,Config0)), - TLS = [{tls,[{reuse_sessions,true}]}], - ACTIVE = [{mode,active}], - PASSIVE = [{mode,passive}], - CaseOpts = case Case of - progress_report_send -> [{progress, {?MODULE,progress,#progress{}}}]; - progress_report_recv -> [{progress, {?MODULE,progress,#progress{}}}]; - _ -> [] - end, - ExtraOpts = [verbose | CaseOpts], - Config = - case Group of - ftp_active -> ftp__open(Config0, ACTIVE ++ ExtraOpts); - ftps_active -> ftp__open(Config0, TLS++ ACTIVE ++ ExtraOpts); - ftp_passive -> ftp__open(Config0, PASSIVE ++ ExtraOpts); - ftps_passive -> ftp__open(Config0, TLS++PASSIVE ++ ExtraOpts); - undefined -> Config0 - end, - case Case of - user -> Config; - bad_user -> Config; - error_elogin -> Config; - error_ehost -> Config; - clean_shutdown -> Config; - _ -> - Pid = proplists:get_value(ftp,Config), - ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS(atom_to_list(Group)++"-"++atom_to_list(Case)) ), - ok = ftp:cd(Pid, proplists:get_value(priv_dir,Config)), - Config - end. - - -end_per_testcase(user, _Config) -> ok; -end_per_testcase(bad_user, _Config) -> ok; -end_per_testcase(error_elogin, _Config) -> ok; -end_per_testcase(error_ehost, _Config) -> ok; -end_per_testcase(clean_shutdown, _Config) -> ok; -end_per_testcase(_Case, Config) -> - case proplists:get_value(tc_status,Config) of - ok -> ok; - _ -> - try ftp:latest_ctrl_response(proplists:get_value(ftp,Config)) - of - {ok,S} -> ct:log("***~n*** Latest ctrl channel response:~n*** ~p~n***",[S]) - catch - _:_ -> ok - end - end, - ftp__close(Config). - -%%-------------------------------------------------------------------- -%% Test Cases -------------------------------------------------------- -%%-------------------------------------------------------------------- -user() -> [ - {doc, "Open an ftp connection to a host, and logon as anonymous ftp," - " then logoff"}]. -user(Config) -> - Pid = proplists:get_value(ftp, Config), - ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS("")),% logon - ok = ftp:close(Pid), % logoff - {error,eclosed} = ftp:pwd(Pid), % check logoff result - ok. - -%%------------------------------------------------------------------------- -bad_user() -> - [{doc, "Open an ftp connection to a host, and logon with bad user."}]. -bad_user(Config) -> - Pid = proplists:get_value(ftp, Config), - {error, euser} = ftp:user(Pid, ?BAD_USER, ?FTP_PASS("")), - ok. - -%%------------------------------------------------------------------------- -pwd() -> - [{doc, "Test ftp:pwd/1 & ftp:lpwd/1"}]. -pwd(Config0) -> - Config = set_state([reset], Config0), - Pid = proplists:get_value(ftp, Config), - {ok, PWD} = ftp:pwd(Pid), - {ok, PathLpwd} = ftp:lpwd(Pid), - PWD = id2ftp_result("", Config), - PathLpwd = id2ftp_result("", Config). - -%%------------------------------------------------------------------------- -cd() -> - ["Open an ftp connection, log on as anonymous ftp, and cd to a" - "directory and to a non-existent directory."]. -cd(Config0) -> - Dir = "test", - Config = set_state([reset,{mkdir,Dir}], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:cd(Pid, id2ftp(Dir,Config)), - {ok, PWD} = ftp:pwd(Pid), - ExpectedPWD = id2ftp_result(Dir, Config), - PWD = ExpectedPWD, - {error, epath} = ftp:cd(Pid, ?BAD_DIR), - ok. - -%%------------------------------------------------------------------------- -lcd() -> - [{doc, "Test api function ftp:lcd/2"}]. -lcd(Config0) -> - Dir = "test", - Config = set_state([reset,{mkdir,Dir}], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:lcd(Pid, id2ftp(Dir,Config)), - {ok, PWD} = ftp:lpwd(Pid), - ExpectedPWD = id2ftp_result(Dir, Config), - PWD = ExpectedPWD, - {error, epath} = ftp:lcd(Pid, ?BAD_DIR). - -%%------------------------------------------------------------------------- -ls() -> - [{doc, "Open an ftp connection; ls the current directory, and the " - "\"test\" directory. We assume that ls never fails, since " - "it's output is meant to be read by humans. "}]. -ls(Config0) -> - Config = set_state([reset,{mkdir,"test"}], Config0), - Pid = proplists:get_value(ftp, Config), - {ok, _R1} = ftp:ls(Pid), - {ok, _R2} = ftp:ls(Pid, id2ftp("test",Config)), - %% neither nlist nor ls operates on a directory - %% they operate on a pathname, which *can* be a - %% directory, but can also be a filename or a group - %% of files (including wildcards). - case proplists:get_value(wildcard_support, Config) of - true -> - {ok, _R3} = ftp:ls(Pid, id2ftp("te*",Config)); - _ -> - ok - end. - -%%------------------------------------------------------------------------- -nlist() -> - [{doc,"Open an ftp connection; nlist the current directory, and the " - "\"test\" directory. Nlist does not behave consistenly over " - "operating systems. On some it is an error to have an empty " - "directory."}]. -nlist(Config0) -> - Config = set_state([reset,{mkdir,"test"}], Config0), - Pid = proplists:get_value(ftp, Config), - {ok, _R1} = ftp:nlist(Pid), - {ok, _R2} = ftp:nlist(Pid, id2ftp("test",Config)), - %% neither nlist nor ls operates on a directory - %% they operate on a pathname, which *can* be a - %% directory, but can also be a filename or a group - %% of files (including wildcards). - case proplists:get_value(wildcard_support, Config) of - true -> - {ok, _R3} = ftp:nlist(Pid, id2ftp("te*",Config)); - _ -> - ok - end. - -%%------------------------------------------------------------------------- -rename() -> - [{doc, "Rename a file."}]. -rename(Config0) -> - Contents = <<"ftp_SUITE test ...">>, - OldFile = "old.txt", - NewFile = "new.txt", - Config = set_state([reset,{mkfile,OldFile,Contents}], Config0), - Pid = proplists:get_value(ftp, Config), - - ok = ftp:rename(Pid, - id2ftp(OldFile,Config), - id2ftp(NewFile,Config)), - - true = (chk_file(NewFile,Contents,Config) - and chk_no_file([OldFile],Config)), - {error,epath} = ftp:rename(Pid, - id2ftp("non_existing_file",Config), - id2ftp(NewFile,Config)), - ok. - -%%------------------------------------------------------------------------- -send() -> - [{doc, "Transfer a file with ftp using send/2."}]. -send(Config0) -> - Contents = <<"ftp_SUITE test ...">>, - SrcDir = "data", - File = "file.txt", - Config = set_state([reset,{mkfile,[SrcDir,File],Contents}], Config0), - Pid = proplists:get_value(ftp, Config), - - chk_no_file([File],Config), - chk_file([SrcDir,File],Contents,Config), - - ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)), - ok = ftp:cd(Pid, id2ftp("",Config)), - ok = ftp:send(Pid, File), - chk_file(File, Contents, Config), - - {error,epath} = ftp:send(Pid, "non_existing_file"), - ok. - -%%------------------------------------------------------------------------- -send_3() -> - [{doc, "Transfer a file with ftp using send/3."}]. -send_3(Config0) -> - Contents = <<"ftp_SUITE test ...">>, - Dir = "incoming", - File = "file.txt", - RemoteFile = "remfile.txt", - Config = set_state([reset,{mkfile,File,Contents},{mkdir,Dir}], Config0), - Pid = proplists:get_value(ftp, Config), - - ok = ftp:cd(Pid, id2ftp(Dir,Config)), - ok = ftp:lcd(Pid, id2ftp("",Config)), - ok = ftp:send(Pid, File, RemoteFile), - chk_file([Dir,RemoteFile], Contents, Config), - - {error,epath} = ftp:send(Pid, "non_existing_file", RemoteFile), - ok. - -%%------------------------------------------------------------------------- -send_bin() -> - [{doc, "Send a binary."}]. -send_bin(Config0) -> - BinContents = <<"ftp_SUITE test ...">>, - File = "file.txt", - Config = set_state([reset], Config0), - Pid = proplists:get_value(ftp, Config), - {error, enotbinary} = ftp:send_bin(Pid, "some string", id2ftp(File,Config)), - ok = ftp:send_bin(Pid, BinContents, id2ftp(File,Config)), - chk_file(File, BinContents, Config), - {error, efnamena} = ftp:send_bin(Pid, BinContents, "/nothere"), - ok. - -%%------------------------------------------------------------------------- -send_chunk() -> - [{doc, "Send a binary using chunks."}]. -send_chunk(Config0) -> - Contents1 = <<"1: ftp_SUITE test ...">>, - Contents2 = <<"2: ftp_SUITE test ...">>, - File = "file.txt", - Config = set_state([reset,{mkdir,"incoming"}], Config0), - Pid = proplists:get_value(ftp, Config), - - ok = ftp:send_chunk_start(Pid, id2ftp(File,Config)), - {error, echunk} = ftp:send_chunk_start(Pid, id2ftp(File,Config)), - {error, echunk} = ftp:cd(Pid, "incoming"), - {error, enotbinary} = ftp:send_chunk(Pid, "some string"), - ok = ftp:send_chunk(Pid, Contents1), - ok = ftp:send_chunk(Pid, Contents2), - ok = ftp:send_chunk_end(Pid), - chk_file(File, <>, Config), - - {error, echunk} = ftp:send_chunk(Pid, Contents1), - {error, echunk} = ftp:send_chunk_end(Pid), - {error, efnamena} = ftp:send_chunk_start(Pid, "/"), - ok. - -%%------------------------------------------------------------------------- -delete() -> - [{doc, "Delete a file."}]. -delete(Config0) -> - Contents = <<"ftp_SUITE test ...">>, - File = "file.txt", - Config = set_state([reset,{mkfile,File,Contents}], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:delete(Pid, id2ftp(File,Config)), - chk_no_file([File], Config), - {error,epath} = ftp:delete(Pid, id2ftp(File,Config)), - ok. - -%%------------------------------------------------------------------------- -mkdir() -> - [{doc, "Make a remote directory."}]. -mkdir(Config0) -> - NewDir = "new_dir", - Config = set_state([reset], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:mkdir(Pid, id2ftp(NewDir,Config)), - chk_dir([NewDir], Config), - {error,epath} = ftp:mkdir(Pid, id2ftp(NewDir,Config)), - ok. - -%%------------------------------------------------------------------------- -rmdir() -> - [{doc, "Remove a directory."}]. -rmdir(Config0) -> - Dir = "dir", - Config = set_state([reset,{mkdir,Dir}], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:rmdir(Pid, id2ftp(Dir,Config)), - chk_no_dir([Dir], Config), - {error,epath} = ftp:rmdir(Pid, id2ftp(Dir,Config)), - ok. - -%%------------------------------------------------------------------------- -append() -> - [{doc, "Append a local file twice to a remote file"}]. -append(Config0) -> - SrcFile = "f_src.txt", - DstFile = "f_dst.txt", - Contents = <<"ftp_SUITE test ...">>, - Config = set_state([reset,{mkfile,SrcFile,Contents}], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)), - ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)), - chk_file(DstFile, <>, Config), - {error,epath} = ftp:append(Pid, id2ftp("non_existing_file",Config), id2ftp(DstFile,Config)), - ok. - -%%------------------------------------------------------------------------- -append_bin() -> - [{doc, "Append a local file twice to a remote file using append_bin"}]. -append_bin(Config0) -> - DstFile = "f_dst.txt", - Contents = <<"ftp_SUITE test ...">>, - Config = set_state([reset], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:append_bin(Pid, Contents, id2ftp(DstFile,Config)), - ok = ftp:append_bin(Pid, Contents, id2ftp(DstFile,Config)), - chk_file(DstFile, <>, Config). - -%%------------------------------------------------------------------------- -append_chunk() -> - [{doc, "Append chunks."}]. -append_chunk(Config0) -> - File = "f_dst.txt", - Contents = [<<"ER">>,<<"LE">>,<<"RL">>], - Config = set_state([reset], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:append_chunk_start(Pid, id2ftp(File,Config)), - {error, enotbinary} = ftp:append_chunk(Pid, binary_to_list(lists:nth(1,Contents))), - ok = ftp:append_chunk(Pid,lists:nth(1,Contents)), - ok = ftp:append_chunk(Pid,lists:nth(2,Contents)), - ok = ftp:append_chunk(Pid,lists:nth(3,Contents)), - ok = ftp:append_chunk_end(Pid), - chk_file(File, <<"ERLERL">>, Config). - -%%------------------------------------------------------------------------- -recv() -> - [{doc, "Receive a file using recv/2"}]. -recv(Config0) -> - File1 = "f_dst1.txt", - File2 = "f_dst2.txt", - SrcDir = "a_dir", - Contents1 = <<"1 ftp_SUITE test ...">>, - Contents2 = <<"2 ftp_SUITE test ...">>, - Config = set_state([reset, {mkfile,[SrcDir,File1],Contents1}, {mkfile,[SrcDir,File2],Contents2}], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:cd(Pid, id2ftp(SrcDir,Config)), - ok = ftp:lcd(Pid, id2ftp("",Config)), - ok = ftp:recv(Pid, File1), - chk_file(File1, Contents1, Config), - ok = ftp:recv(Pid, File2), - chk_file(File2, Contents2, Config), - {error,epath} = ftp:recv(Pid, "non_existing_file"), - ok. - -%%------------------------------------------------------------------------- -recv_3() -> - [{doc,"Receive a file using recv/3"}]. -recv_3(Config0) -> - DstFile = "f_src.txt", - SrcFile = "f_dst.txt", - Contents = <<"ftp_SUITE test ...">>, - Config = set_state([reset, {mkfile,SrcFile,Contents}], Config0), - Pid = proplists:get_value(ftp, Config), - ok = ftp:cd(Pid, id2ftp("",Config)), - ok = ftp:recv(Pid, SrcFile, id2abs(DstFile,Config)), - chk_file(DstFile, Contents, Config). - -%%------------------------------------------------------------------------- -recv_bin() -> - [{doc, "Receive a file as a binary."}]. -recv_bin(Config0) -> - File = "f_dst.txt", - Contents = <<"ftp_SUITE test ...">>, - Config = set_state([reset, {mkfile,File,Contents}], Config0), - Pid = proplists:get_value(ftp, Config), - {ok,Received} = ftp:recv_bin(Pid, id2ftp(File,Config)), - find_diff(Received, Contents), - {error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)), - ok. - -%%------------------------------------------------------------------------- -recv_bin_twice() -> - [{doc, "Receive two files as a binaries."}]. -recv_bin_twice(Config0) -> - File1 = "f_dst1.txt", - File2 = "f_dst2.txt", - Contents1 = <<"1 ftp_SUITE test ...">>, - Contents2 = <<"2 ftp_SUITE test ...">>, - Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}], Config0), - ct:log("First transfer",[]), - Pid = proplists:get_value(ftp, Config), - {ok,Received1} = ftp:recv_bin(Pid, id2ftp(File1,Config)), - find_diff(Received1, Contents1), - ct:log("Second transfer",[]), - {ok,Received2} = ftp:recv_bin(Pid, id2ftp(File2,Config)), - find_diff(Received2, Contents2), - ct:log("Transfers ready!",[]), - {error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)), - ok. -%%------------------------------------------------------------------------- -recv_chunk() -> - [{doc, "Receive a file using chunk-wise."}]. -recv_chunk(Config0) -> - File = "big_file.txt", - Contents = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ), - Config = set_state([reset, {mkfile,File,Contents}], Config0), - Pid = proplists:get_value(ftp, Config), - {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>), - ok = ftp:recv_chunk_start(Pid, id2ftp(File,Config)), - {ok, ReceivedContents, _Ncunks} = recv_chunk(Pid, <<>>), - find_diff(ReceivedContents, Contents). - -recv_chunk_twice() -> - [{doc, "Receive two files using chunk-wise."}]. -recv_chunk_twice(Config0) -> - File1 = "big_file1.txt", - File2 = "big_file2.txt", - Contents1 = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ), - Contents2 = crypto:strong_rand_bytes(1200), - Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}], Config0), - Pid = proplists:get_value(ftp, Config), - {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>), - ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)), - {ok, ReceivedContents1, _Ncunks1} = recv_chunk(Pid, <<>>), - ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)), - {ok, ReceivedContents2, _Ncunks2} = recv_chunk(Pid, <<>>), - find_diff(ReceivedContents1, Contents1), - find_diff(ReceivedContents2, Contents2). - -recv_chunk_three_times() -> - [{doc, "Receive two files using chunk-wise."}, - {timetrap,{seconds,120}}]. -recv_chunk_three_times(Config0) -> - File1 = "big_file1.txt", - File2 = "big_file2.txt", - File3 = "big_file3.txt", - Contents1 = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ), - Contents2 = crypto:strong_rand_bytes(1200), - Contents3 = list_to_binary( lists:duplicate(1000, lists:seq(255,0,-1)) ), - - Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}, {mkfile,File3,Contents3}], Config0), - Pid = proplists:get_value(ftp, Config), - {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>), - - ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)), - {ok, ReceivedContents1, Nchunks1} = recv_chunk(Pid, <<>>), - - ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)), - {ok, ReceivedContents2, _Nchunks2} = recv_chunk(Pid, <<>>), - - ok = ftp:recv_chunk_start(Pid, id2ftp(File3,Config)), - {ok, ReceivedContents3, _Nchunks3} = recv_chunk(Pid, <<>>, 10000, 0, Nchunks1), - - find_diff(ReceivedContents1, Contents1), - find_diff(ReceivedContents2, Contents2), - find_diff(ReceivedContents3, Contents3). - - - -recv_chunk(Pid, Acc) -> - recv_chunk(Pid, Acc, 0, 0, undefined). - - - -%% ExpectNchunks :: integer() | undefined -recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) when N+1 < ExpectNchunks -> - %% for all I in integer(), I < undefined - recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks); - -recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) -> - %% N >= ExpectNchunks-1 - timer:sleep(DelayMilliSec), - recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks). - - -recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks) -> - ct:log("Call ftp:recv_chunk",[]), - case ftp:recv_chunk(Pid) of - ok -> {ok, Acc, N}; - {ok, Bin} -> recv_chunk(Pid, <>, DelayMilliSec, N+1, ExpectNchunks); - Error -> {Error, N} - end. - -%%------------------------------------------------------------------------- -type() -> - [{doc,"Test that we can change btween ASCCI and binary transfer mode"}]. -type(Config) -> - Pid = proplists:get_value(ftp, Config), - ok = ftp:type(Pid, ascii), - ok = ftp:type(Pid, binary), - ok = ftp:type(Pid, ascii), - {error, etype} = ftp:type(Pid, foobar). - -%%------------------------------------------------------------------------- -quote(Config) -> - Pid = proplists:get_value(ftp, Config), - ["257 \""++_Rest] = ftp:quote(Pid, "pwd"), %% 257 - [_| _] = ftp:quote(Pid, "help"), - %% This negativ test causes some ftp servers to hang. This test - %% is not important for the client, so we skip it for now. - %%["425 Can't build data connection: Connection refused."] - %% = ftp:quote(Pid, "list"), - ok. - -%%------------------------------------------------------------------------- -progress_report_send() -> - [{doc, "Test the option progress for ftp:send/[2,3]"}]. -progress_report_send(Config) when is_list(Config) -> - ReportPid = - spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]), - send(Config), - receive - {ReportPid, ok} -> - ok - end. - -%%------------------------------------------------------------------------- -progress_report_recv() -> - [{doc, "Test the option progress for ftp:recv/[2,3]"}]. -progress_report_recv(Config) when is_list(Config) -> - ReportPid = - spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]), - recv(Config), - receive - {ReportPid, ok} -> - ok - end. - -%%------------------------------------------------------------------------- - -not_owner() -> - [{doc, "Test what happens if a process that not owns the connection tries " - "to use it"}]. -not_owner(Config) when is_list(Config) -> - Pid = proplists:get_value(ftp, Config), - - Parent = self(), - OtherPid = spawn_link( - fun() -> - {error, not_connection_owner} = ftp:pwd(Pid), - ftp:close(Pid), - Parent ! {self(), ok} - end), - receive - {OtherPid, ok} -> - {ok, _} = ftp:pwd(Pid) - end. - - -%%------------------------------------------------------------------------- - - -unexpected_call()-> - [{doc, "Test that behaviour of the ftp process if the api is abused"}]. -unexpected_call(Config) when is_list(Config) -> - Flag = process_flag(trap_exit, true), - Pid = proplists:get_value(ftp, Config), - - %% Serious programming fault, connetion will be shut down - case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of - {error, {connection_terminated, 'API_violation'}} -> - ok; - Unexpected1 -> - exit({unexpected_result, Unexpected1}) - end, - ct:sleep(500), - undefined = process_info(Pid, status), - process_flag(trap_exit, Flag). -%%------------------------------------------------------------------------- - -unexpected_cast()-> - [{doc, "Test that behaviour of the ftp process if the api is abused"}]. -unexpected_cast(Config) when is_list(Config) -> - Flag = process_flag(trap_exit, true), - Pid = proplists:get_value(ftp, Config), - %% Serious programming fault, connetion will be shut down - gen_server:cast(Pid, {self(), foobar, 10}), - ct:sleep(500), - undefined = process_info(Pid, status), - process_flag(trap_exit, Flag). -%%------------------------------------------------------------------------- - -unexpected_bang()-> - [{doc, "Test that connection ignores unexpected bang"}]. -unexpected_bang(Config) when is_list(Config) -> - Flag = process_flag(trap_exit, true), - Pid = proplists:get_value(ftp, Config), - %% Could be an innocent misstake the connection lives. - Pid ! foobar, - ct:sleep(500), - {status, _} = process_info(Pid, status), - process_flag(trap_exit, Flag). - -%%------------------------------------------------------------------------- - -clean_shutdown() -> - [{doc, "Test that owning process that exits with reason " - "'shutdown' does not cause an error message. OTP 6035"}]. - -clean_shutdown(Config) -> - Parent = self(), - HelperPid = spawn( - fun() -> - ftp__open(Config, [verbose]), - Parent ! ok, - receive - nothing -> ok - end - end), - receive - ok -> - PrivDir = proplists:get_value(priv_dir, Config), - LogFile = filename:join([PrivDir,"ticket_6035.log"]), - error_logger:logfile({open, LogFile}), - exit(HelperPid, shutdown), - timer:sleep(2000), - error_logger:logfile(close), - case is_error_report_6035(LogFile) of - true -> ok; - false -> {fail, "Bad logfile"} - end - end. - -%%%---------------------------------------------------------------- -%%% Error codes not tested elsewhere - -error_elogin(Config0) -> - Dir = "test", - OldFile = "old.txt", - NewFile = "new.txt", - SrcDir = "data", - File = "file.txt", - Config = set_state([reset, - {mkdir,Dir}, - {mkfile,OldFile,<<"Contents..">>}, - {mkfile,[SrcDir,File],<<"Contents..">>}], Config0), - - Pid = proplists:get_value(ftp, Config), - ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)), - {error,elogin} = ftp:send(Pid, File), - ok = ftp:lcd(Pid, id2ftp("",Config)), - {error,elogin} = ftp:pwd(Pid), - {error,elogin} = ftp:cd(Pid, id2ftp(Dir,Config)), - {error,elogin} = ftp:rename(Pid, - id2ftp(OldFile,Config), - id2ftp(NewFile,Config)), - ok. - -error_ehost(_Config) -> - {error, ehost} = ftp:open("nohost.nodomain"), - ok. - -%%-------------------------------------------------------------------- -%% Internal functions ----------------------------------------------- -%%-------------------------------------------------------------------- - -make_cert_files(Alg1, Alg2, Prefix, Dir) -> - CaInfo = {CaCert,_} = erl_make_certs:make_cert([{key,Alg1}]), - {Cert,CertKey} = erl_make_certs:make_cert([{key,Alg2},{issuer,CaInfo}]), - CaCertFile = filename:join(Dir, Prefix++"cacerts.pem"), - CertFile = filename:join(Dir, Prefix++"cert.pem"), - KeyFile = filename:join(Dir, Prefix++"key.pem"), - der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]), - der_to_pem(CertFile, [{'Certificate', Cert, not_encrypted}]), - der_to_pem(KeyFile, [CertKey]), - ok. - -der_to_pem(File, Entries) -> - PemBin = public_key:pem_encode(Entries), - file:write_file(File, PemBin). - -%%-------------------------------------------------------------------- -chk_file(Path=[C|_], ExpectedContents, Config) when 0 - chk_file([Path], ExpectedContents, Config); - -chk_file(PathList, ExpectedContents, Config) -> - Path = filename:join(PathList), - AbsPath = id2abs(Path,Config), - case file:read_file(AbsPath) of - {ok,ExpectedContents} -> - true; - {ok,ReadContents} -> - {error,{diff,Pos,RC,LC}} = find_diff(ReadContents, ExpectedContents, 1), - ct:log("Bad contents of ~p.~nGot:~n~p~nExpected:~n~p~nDiff at pos ~p ~nRead: ~p~nExp : ~p", - [AbsPath,ReadContents,ExpectedContents,Pos,RC,LC]), - ct:fail("Bad contents of ~p", [Path]); - {error,Error} -> - try begin - {ok,CWD} = file:get_cwd(), - ct:log("file:get_cwd()=~p~nfiles:~n~p",[CWD,file:list_dir(CWD)]) - end - of _ -> ok - catch _:_ ->ok - end, - ct:fail("Error reading ~p: ~p",[Path,Error]) - end. - - -chk_no_file(Path=[C|_], Config) when 0 - chk_no_file([Path], Config); - -chk_no_file(PathList, Config) -> - Path = filename:join(PathList), - AbsPath = id2abs(Path,Config), - case file:read_file(AbsPath) of - {error,enoent} -> - true; - {ok,Contents} -> - ct:log("File ~p exists although it shouldn't. Contents:~n~p", - [AbsPath,Contents]), - ct:fail("File exists: ~p", [Path]); - {error,Error} -> - ct:fail("Unexpected error reading ~p: ~p",[Path,Error]) - end. - - -chk_dir(Path=[C|_], Config) when 0 - chk_dir([Path], Config); - -chk_dir(PathList, Config) -> - Path = filename:join(PathList), - AbsPath = id2abs(Path,Config), - case file:read_file_info(AbsPath) of - {ok, #file_info{type=directory}} -> - true; - {ok, #file_info{type=Type}} -> - ct:fail("Expected dir ~p is a ~p",[Path,Type]); - {error,Error} -> - ct:fail("Expected dir ~p: ~p",[Path,Error]) - end. - -chk_no_dir(PathList, Config) -> - Path = filename:join(PathList), - AbsPath = id2abs(Path,Config), - case file:read_file_info(AbsPath) of - {error,enoent} -> - true; - {ok, #file_info{type=directory}} -> - ct:fail("Dir ~p erroneously exists",[Path]); - {ok, #file_info{type=Type}} -> - ct:fail("~p ~p erroneously exists",[Type,Path]); - {error,Error} -> - ct:fail("Unexpected error for ~p: ~p",[Path,Error]) - end. - -%%-------------------------------------------------------------------- -find_executable(Config) -> - search_executable(proplists:get_value(ftpservers, Config, ?default_ftp_servers)). - - -search_executable([{Name,Paths,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}|Srvrs]) -> - case os_find(Name,Paths) of - false -> - ct:log("~p not found",[Name]), - search_executable(Srvrs); - AbsName -> - ct:comment("Found ~p",[AbsName]), - {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}} - end; -search_executable([]) -> - false. - - -os_find(Name, Paths) -> - case os:find_executable(Name, Paths) of - false -> os:find_executable(Name); - AbsName -> AbsName - end. - -%%%---------------------------------------------------------------- -start_ftpd(Config0) -> - {AbsName,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} = - proplists:get_value(ftpd_data, Config0), - case StartCmd(Config0, AbsName) of - {ok,StartResult} -> - Config = [{ftpd_host,Host}, - {ftpd_port,Port}, - {ftpd_start_result,StartResult} | ConfigRewrite(Config0)], - try - ftp__close(ftp__open(Config,[verbose])) - of - Config1 when is_list(Config1) -> - ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]), - Config - catch - Class:Exception -> - ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p", - [AbsName,Host,Port,Class,Exception]), - {skip, [AbsName," started but unusuable"]} - end; - {error,Msg} -> - {skip, [AbsName," not started: ",Msg]} - end. - -stop_ftpd(Config) -> - {_Name,_StartCmd,_ChkUp,StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), - StopCommand(proplists:get_value(ftpd_start_result,Config)). - -ps_ftpd(Config) -> - {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), - ct:log( ChkUp(proplists:get_value(ftpd_start_result,Config)) ). - - -ftpd_running(Config) -> - {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), - ChkUp(proplists:get_value(ftpd_start_result,Config)). - -ftp__open(Config, Options) -> - Host = proplists:get_value(ftpd_host,Config), - Port = proplists:get_value(ftpd_port,Config), - ct:log("Host=~p, Port=~p",[Host,Port]), - {ok,Pid} = ftp:open(Host, [{port,Port} | Options]), - [{ftp,Pid}|Config]. - -ftp__close(Config) -> - ok = ftp:close(proplists:get_value(ftp,Config)), - Config. - -split(Cs) -> string:tokens(Cs, "\r\n"). - -find_diff(Bin1, Bin2) -> - case find_diff(Bin1, Bin2, 1) of - {error, {diff,Pos,RC,LC}} -> - ct:log("Contents differ at position ~p.~nOp1: ~p~nOp2: ~p",[Pos,RC,LC]), - ct:fail("Contents differ at pos ~p",[Pos]); - Other -> - Other - end. - -find_diff(A, A, _) -> true; -find_diff(<>, <>, Pos) -> find_diff(T1, T2, Pos+1); -find_diff(RC, LC, Pos) -> {error, {diff, Pos, RC, LC}}. - -set_state(Ops, Config) when is_list(Ops) -> lists:foldl(fun set_state/2, Config, Ops); - -set_state(reset, Config) -> - rm('*', id2abs("",Config)), - PrivDir = proplists:get_value(priv_dir,Config), - file:set_cwd(PrivDir), - ftp:lcd(proplists:get_value(ftp,Config),PrivDir), - set_state({mkdir,""},Config); -set_state({mkdir,Id}, Config) -> - Abs = id2abs(Id, Config), - mk_path(Abs), - file:make_dir(Abs), - Config; -set_state({mkfile,Id,Contents}, Config) -> - Abs = id2abs(Id, Config), - mk_path(Abs), - ok = file:write_file(Abs, Contents), - Config. - -mk_path(Abs) -> lists:foldl(fun mk_path/2, [], filename:split(filename:dirname(Abs))). - -mk_path(F, Pfx) -> - case file:read_file_info(AbsName=filename:join(Pfx,F)) of - {ok,#file_info{type=directory}} -> - AbsName; - {error,eexist} -> - AbsName; - {error,enoent} -> - ok = file:make_dir(AbsName), - AbsName - end. - -rm('*', Pfx) -> - {ok,Fs} = file:list_dir(Pfx), - lists:foreach(fun(F) -> rm(F, Pfx) end, Fs); -rm(F, Pfx) -> - case file:read_file_info(AbsName=filename:join(Pfx,F)) of - {ok,#file_info{type=directory}} -> - {ok,Fs} = file:list_dir(AbsName), - lists:foreach(fun(F1) -> rm(F1,AbsName) end, Fs), - ok = file:del_dir(AbsName); - - {ok,#file_info{type=regular}} -> - ok = file:delete(AbsName); - - {error,enoent} -> - ok - end. - -id2abs(Id, Conf) -> filename:join(proplists:get_value(priv_dir,Conf),ids(Id)). -id2ftp(Id, Conf) -> (proplists:get_value(id2ftp,Conf))(ids(Id)). -id2ftp_result(Id, Conf) -> (proplists:get_value(id2ftp_result,Conf))(ids(Id)). - -ids([[_|_]|_]=Ids) -> filename:join(Ids); -ids(Id) -> Id. - - -is_expected_absName(Id, File, Conf) -> File = (proplists:get_value(id2abs,Conf))(Id). -is_expected_ftpInName(Id, File, Conf) -> File = (proplists:get_value(id2ftp,Conf))(Id). -is_expected_ftpOutName(Id, File, Conf) -> File = (proplists:get_value(id2ftp_result,Conf))(Id). - - -%%%---------------------------------------------------------------- -%%% Help functions for the option '{progress,Progress}' -%%% - -%%%---------------- -%%% Callback: - -progress(#progress{} = P, _File, {file_size, Total} = M) -> - ct:pal("Progress: ~p",[M]), - progress_report_receiver ! start, - P#progress{total = Total}; - -progress(#progress{current = Current} = P, _File, {transfer_size, 0} = M) -> - ct:pal("Progress: ~p",[M]), - progress_report_receiver ! finish, - case P#progress.total of - unknown -> P; - Current -> P; - Total -> ct:fail({error, {progress, {total,Total}, {current,Current}}}), - P - end; - -progress(#progress{current = Current} = P, _File, {transfer_size, Size} = M) -> - ct:pal("Progress: ~p",[M]), - progress_report_receiver ! update, - P#progress{current = Current + Size}; - -progress(P, _File, M) -> - ct:pal("Progress **** Strange: ~p",[M]), - P. - - -%%%---------------- -%%% Help process that counts the files transferred: - -progress_report_receiver_init(Parent, N) -> - register(progress_report_receiver, self()), - progress_report_receiver_expect_N_files(Parent, N). - -progress_report_receiver_expect_N_files(_Parent, 0) -> - ct:pal("progress_report got all files!", []); -progress_report_receiver_expect_N_files(Parent, N) -> - ct:pal("progress_report expects ~p more files",[N]), - receive - start -> ok - end, - progress_report_receiver_loop(Parent, N-1). - - -progress_report_receiver_loop(Parent, N) -> - ct:pal("progress_report expect update | finish. N = ~p",[N]), - receive - update -> - ct:pal("progress_report got update",[]), - progress_report_receiver_loop(Parent, N); - finish -> - ct:pal("progress_report got finish, send ~p to ~p",[{self(),ok}, Parent]), - Parent ! {self(), ok}, - progress_report_receiver_expect_N_files(Parent, N) - end. - -%%%---------------------------------------------------------------- -%%% Help functions for bug OTP-6035 - -is_error_report_6035(LogFile) -> - case file:read_file(LogFile) of - {ok, Bin} -> - nomatch =/= binary:match(Bin, <<"=ERROR REPORT====">>); - _ -> - false - end. - diff --git a/lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel b/lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel deleted file mode 100644 index 75096ce687..0000000000 --- a/lib/inets/test/ftp_SUITE_data/ftpd_hosts.skel +++ /dev/null @@ -1,18 +0,0 @@ -%% Add a host name in the appropriate list -%% Each "platform" contains a list of hostnames (a string) that can -%% be used for testing the ftp client. -%% The definition below are an example!! -[{solaris8_sparc, ["solaris8_sparc_dummy1", "solaris8_sparc_dummy2"]}, - {solaris9_sparc, ["solaris9_sparc_dummy1"]}, - {solaris10_sparc, ["solaris10_sparc_dummy1"]}, - {solaris10_x86, ["solaris10_x86_dummy1", "solaris10_x86_dummy2"]}, - {linux_x86, ["linux_x86_dummy1", "linux_x86_dummy2"]}, - {linux_ppc, ["linux_ppc_dummy1"]}, - {macosx_ppc, ["macosx_ppc_dummy1"]}, - {macosx_x86, ["macosx_x86_dummy1", "macosx_x86_dummy2"]}, - {openbsd_x86, []}, - {freebsd_x86, ["freebsd_x86_dummy1"]}, - {netbsd_x86, []}, - {windows_xp, []}, - {windows_2003_server, ["win2003_dummy1"]}, - {ticket_test, ["solaris8_x86_dummy1", "linux_x86_dummy1"]}]. diff --git a/lib/inets/test/ftp_SUITE_data/vsftpd.conf b/lib/inets/test/ftp_SUITE_data/vsftpd.conf deleted file mode 100644 index a5584f5916..0000000000 --- a/lib/inets/test/ftp_SUITE_data/vsftpd.conf +++ /dev/null @@ -1,26 +0,0 @@ - -### -### Some parameters are given in the vsftpd start command. -### -### Typical command-line paramters are such that has a file path -### component like cert files. -### - - -listen=YES -listen_port=9999 -run_as_launching_user=YES -ssl_enable=YES -allow_anon_ssl=YES - -background=YES - -write_enable=YES -anonymous_enable=YES -anon_upload_enable=YES -anon_mkdir_write_enable=YES -anon_other_write_enable=YES -anon_world_readable_only=NO - -### Shouldn't be necessary.... -require_ssl_reuse=NO diff --git a/lib/inets/test/ftp_format_SUITE.erl b/lib/inets/test/ftp_format_SUITE.erl deleted file mode 100644 index 95d594a44b..0000000000 --- a/lib/inets/test/ftp_format_SUITE.erl +++ /dev/null @@ -1,328 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% --module(ftp_format_SUITE). --author('ingela@erix.ericsson.se'). - --include_lib("common_test/include/ct.hrl"). --include("ftp_internal.hrl"). - -%% Note: This directive should only be used in test suites. --compile(export_all). - -suite() -> - [{ct_hooks,[ts_install_cth]}, - {timetrap,{seconds,5}} - ]. - -all() -> - [{group, ftp_response}, format_error]. - -groups() -> - [{ftp_response, [], - [ftp_150, ftp_200, ftp_220, ftp_226, ftp_257, ftp_331, - ftp_425, ftp_other_status_codes, ftp_multiple_lines_status_in_msg, - ftp_multiple_lines, ftp_multipel_ctrl_messages]}]. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - - -init_per_testcase(_, Config) -> - Config. -end_per_testcase(_, _) -> - ok. - -%%------------------------------------------------------------------------- -%% Test cases starts here. -%%------------------------------------------------------------------------- - -ftp_150() -> - [{doc, "Especially check that respons can be devided in a random place."}]. -ftp_150(Config) when is_list(Config) -> - FtpResponse = ["150 ASCII data conn", "ection for /bin/ls ", - "(134.138.177", ".89,50434) (0 bytes).\r\n"], - - "150 ASCII data connection for /bin/ls " - "(134.138.177.89,50434) (0 bytes).\r\n" = Msg = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_prel, _} = ftp_response:interpret(Msg). - -ftp_200() -> - [{doc, "Especially check that respons can be devided after the first status " - "code character and in the end delimiter."}]. -ftp_200(Config) when is_list(Config) -> - FtpResponse = ["2", "00 PORT command successful.", [?CR], [?LF]], - - "200 PORT command successful.\r\n" = Msg = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_compl, _} = ftp_response:interpret(Msg), - ok. - -ftp_220() -> - [{doc, "Especially check that respons can be devided after the " - "first with space "}]. -ftp_220(Config) when is_list(Config) -> - FtpResponse = ["220 ","fingon FTP server (SunOS 5.8) ready.\r\n"], - - "220 fingon FTP server (SunOS 5.8) ready.\r\n" = Msg = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_compl, _} = ftp_response:interpret(Msg), - ok. - -ftp_226() -> - [{doc, "Especially check that respons can be devided after second status code" - " character and in the end delimiter."}]. -ftp_226(Config) when is_list(Config) -> - FtpResponse = ["22" "6 Transfer complete.\r", [?LF]], - - "226 Transfer complete.\r\n" = Msg = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_compl, _} = ftp_response:interpret(Msg), - ok. - -ftp_257() -> - [{doc, "Especially check that quoted chars do not cause a problem."}]. -ftp_257(Config) when is_list(Config) -> - FtpResponse = ["257 \"/\" is current directory.\r\n"], - - "257 \"/\" is current directory.\r\n" = Msg = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_compl, _} = ftp_response:interpret(Msg), - ok. - -ftp_331() -> - [{doc, "Especially check that respons can be devided after the third status " - " status code character."}]. -ftp_331(Config) when is_list(Config) -> - %% Brake before white space after code - FtpResponse = - ["331"," Guest login ok, send ient as password.\r\n"], - - "331 Guest login ok, send ient as password.\r\n" = Msg = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_interm, _} = ftp_response:interpret(Msg), - ok. - -ftp_425() -> - [{doc, "Especially check a message that was received in only one part."}]. -ftp_425(Config) when is_list(Config) -> - FtpResponse = - ["425 Can't build data connection: Connection refused.\r\n"], - - "425 Can't build data connection: Connection refused.\r\n" - = Msg = parse(ftp_response, parse_lines, [[], start], FtpResponse), - {trans_neg_compl, _} = ftp_response:interpret(Msg), - ok. - -ftp_multiple_lines_status_in_msg() -> - [{doc, "check that multiple lines gets parsed correct, even if we have " - " the status code within the msg being sent"}]. -ftp_multiple_lines_status_in_msg(Config) when is_list(Config) -> - ML = "230-User usr-230 is logged in\r\n" ++ - "230 OK. Current directory is /\r\n", - {ok, ML, <<>>} = ftp_response:parse_lines(list_to_binary(ML), [], start), - ok. - -ftp_multiple_lines() -> - [{doc, "Especially check multiple lines devided in significant places"}]. -ftp_multiple_lines(Config) when is_list(Config) -> - FtpResponse = ["21", "4","-The", - " following commands are recognized:\r\n" - " USER EPRT STRU MAIL* ALLO CWD", - " STAT* XRMD \r\n" - " PASS LPRT MODE MSND* " - " REST* XCWD HELP PWD ", [?CRLF], - " ACCT* EPSV RETR MSOM* RNFR LIST " - " NOOP XPWD \r\n", - " REIN* LPSV STOR MSAM* RNTO NLST " - " MKD CDUP \r\n" - " QUIT PASV APPE MRSQ* ABOR SITE* " - " XMKD XCUP \r\n" - " PORT TYPE MLFL* MRCP* DELE SYST " - " RMD STOU \r\n" - "214 (*'s => unimplemented)", [?CR], [?LF]], - - - FtpResponse1 = ["214-", "The", - " following commands are recognized:\r\n" - " USER EPRT STRU MAIL* ALLO CWD", - " STAT* XRMD \r\n" - " PASS LPRT MODE MSND* " - " REST* XCWD HELP PWD ", [?CRLF], - " ACCT* EPSV RETR MSOM* RNFR LIST " - " NOOP XPWD \r\n", - " REIN* LPSV STOR MSAM* RNTO NLST " - " MKD CDUP \r\n" - " QUIT PASV APPE MRSQ* ABOR SITE* " - " XMKD XCUP \r\n" - " PORT TYPE MLFL* MRCP* DELE SYST " - " RMD STOU \r\n" - "2", "14 (*'s => unimplemented)", [?CR], [?LF]], - - FtpResponse2 = ["214-", "The", - " following commands are recognized:\r\n" - " USER EPRT STRU MAIL* ALLO CWD", - " STAT* XRMD \r\n" - " PASS LPRT MODE MSND* " - " REST* XCWD HELP PWD ", [?CRLF], - " ACCT* EPSV RETR MSOM* RNFR LIST " - " NOOP XPWD \r\n", - " REIN* LPSV STOR MSAM* RNTO NLST " - " MKD CDUP \r\n" - " QUIT PASV APPE MRSQ* ABOR SITE* " - " XMKD XCUP \r\n" - " PORT TYPE MLFL* MRCP* DELE SYST " - " RMD STOU \r\n" - "21", "4"," (*'s => unimplemented)", [?CR], [?LF]], - - MultiLineResultStr = - "214-The following commands are recognized:\r\n" - " USER EPRT STRU MAIL* ALLO CWD STAT* " - "XRMD \r\n" - " PASS LPRT MODE MSND* REST* XCWD HELP " - "PWD \r\n" - " ACCT* EPSV RETR MSOM* RNFR LIST NOOP " - "XPWD \r\n" - " REIN* LPSV STOR MSAM* RNTO NLST MKD " - "CDUP \r\n" - " QUIT PASV APPE MRSQ* ABOR SITE* XMKD " - "XCUP \r\n" - " PORT TYPE MLFL* MRCP* DELE SYST RMD " - "STOU \r\n" - "214 (*'s => unimplemented)\r\n", - - MultiLineResultStr = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_compl, _} = ftp_response:interpret(MultiLineResultStr), - - MultiLineResultStr = parse(ftp_response, parse_lines, [[], start], - FtpResponse1), - - MultiLineResultStr = parse(ftp_response, parse_lines, [[], start], - FtpResponse2), - ok. - -ftp_other_status_codes() -> - [{doc, "Check that other valid status codes, than the ones above, are handled" - "by ftp_response:interpret/1. Note there are som ftp status codes" - "that will not be received with the current ftp instruction support," - "they are not included here."}]. -ftp_other_status_codes(Config) when is_list(Config) -> - - %% 1XX - {pos_prel, _ } = ftp_response:interpret("120 Foobar\r\n"), - - %% 2XX - {pos_compl, _ } = ftp_response:interpret("202 Foobar\r\n"), - {pos_compl, _ } = ftp_response:interpret("221 Foobar\r\n"), - {pos_compl, _ } = ftp_response:interpret("227 Foobar\r\n"), - {pos_compl, _ } = ftp_response:interpret("230 Foobar\r\n"), - {pos_compl, _ } = ftp_response:interpret("250 Foobar\r\n"), - - %% 3XX - {pos_interm_acct, _ } = ftp_response:interpret("332 Foobar\r\n"), - {pos_interm, _ } = ftp_response:interpret("350 Foobar\r\n"), - - %% 4XX - {trans_neg_compl, _ } = ftp_response:interpret("421 Foobar\r\n"), - {trans_neg_compl, _ } = ftp_response:interpret("426 Foobar\r\n"), - {enofile, _ } = ftp_response:interpret("450 Foobar\r\n"), - {trans_neg_compl, _ } = ftp_response:interpret("451 Foobar\r\n"), - {etnospc, _ } = ftp_response:interpret("452 Foobar\r\n"), - - %% 5XX - {perm_neg_compl, _ } = ftp_response:interpret("500 Foobar\r\n"), - {perm_neg_compl, _ } = ftp_response:interpret("501 Foobar\r\n"), - {perm_neg_compl, _ } = ftp_response:interpret("503 Foobar\r\n"), - {perm_neg_compl, _ } = ftp_response:interpret("504 Foobar\r\n"), - {elogin, _ } = ftp_response:interpret("530 Foobar\r\n"), - {perm_neg_compl, _ } = ftp_response:interpret("532 Foobar\r\n"), - {epath, _ } = ftp_response:interpret("550 Foobar\r\n"), - {epnospc, _ } = ftp_response:interpret("552 Foobar\r\n"), - {efnamena, _ } = ftp_response:interpret("553 Foobar\r\n"), - ok. - -ftp_multipel_ctrl_messages() -> - [{doc, "The ftp server may send more than one control message as a reply," - "check that they are handled one at the time."}]. -ftp_multipel_ctrl_messages(Config) when is_list(Config) -> - FtpResponse = ["200 PORT command successful.\r\n200 Foobar\r\n"], - - {"200 PORT command successful.\r\n" = Msg, NextMsg} = - parse(ftp_response, parse_lines, [[], start], FtpResponse), - {pos_compl, _} = ftp_response:interpret(Msg), - NewMsg = parse(ftp_response, parse_lines, [[], start], NextMsg), - {pos_compl, _} = ftp_response:interpret(NewMsg), - ok. - - -%%------------------------------------------------------------------------- -format_error(Config) when is_list(Config) -> - "Synchronisation error during chunk sending." = - ftp:formaterror(echunk), - "Session has been closed." = ftp:formaterror(eclosed), - "Connection to remote server prematurely closed." = - ftp:formaterror(econn), - "File or directory already exists." = ftp:formaterror(eexists), - "Host not found, FTP server not found, or connection rejected." = - ftp:formaterror(ehost), - "User not logged in." = ftp:formaterror(elogin), - "Term is not a binary." = ftp:formaterror(enotbinary), - "No such file or directory, already exists, or permission denied." - = ftp:formaterror(epath), - "No such type." = ftp:formaterror(etype), - "User name or password not valid." = ftp:formaterror(euser), - "Insufficient storage space in system." = ftp:formaterror(etnospc), - "Exceeded storage allocation (for current directory or dataset)." - = ftp:formaterror(epnospc), - "File name not allowed." = ftp:formaterror(efnamena), - "Unknown error: foobar" = ftp:formaterror({error, foobar}). - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- -parse(Module, Function, Args, Bin) when is_binary(Bin) -> - parse(Module, Function, Args, [binary_to_list(Bin)]); - -parse(Module, Function, [AccLines, StatusCode], [Data | Rest]) -> - case Module:Function(list_to_binary(Data), AccLines, StatusCode) of - {ok, Result, <<>>} -> - Result; - {ok, Result, Next} -> - {Result, Next}; - {continue, {NewData, NewAccLines, NewStatusCode}} -> - case Rest of - [] -> - ct:fail({wrong_input, Data, Rest}); - [_ | _] -> - parse(Module, Function, [NewAccLines, NewStatusCode], - [binary_to_list(NewData) ++ hd(Rest) | tl(Rest)]) - end - end. diff --git a/lib/inets/test/ftp_internal.hrl b/lib/inets/test/ftp_internal.hrl deleted file mode 120000 index af57081f14..0000000000 --- a/lib/inets/test/ftp_internal.hrl +++ /dev/null @@ -1 +0,0 @@ -../src/ftp/ftp_internal.hrl \ No newline at end of file diff --git a/lib/inets/test/ftp_property_test_SUITE.erl b/lib/inets/test/ftp_property_test_SUITE.erl deleted file mode 100644 index b314882296..0000000000 --- a/lib/inets/test/ftp_property_test_SUITE.erl +++ /dev/null @@ -1,53 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% -%% - -%%% Run like this: -%%% ct:run_test([{suite,"ftp_property_test_SUITE"}, {logdir,"/ldisk/OTP/LOG"}]). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% %%% -%%% WARNING %%% -%%% %%% -%%% This is experimental code which may be changed or removed %%% -%%% anytime without any warning. %%% -%%% %%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --module(ftp_property_test_SUITE). - --compile(export_all). - --include_lib("common_test/include/ct.hrl"). - -all() -> [prop_ftp_case]. - - -init_per_suite(Config) -> - inets:start(), - ct_property_test:init_per_suite(Config). - - -%%%---- test case -prop_ftp_case(Config) -> - ct_property_test:quickcheck( - ftp_simple_client_server:prop_ftp(Config), - Config - ). diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl index 1abd96a228..12db2479d2 100644 --- a/lib/inets/test/inets_SUITE.erl +++ b/lib/inets/test/inets_SUITE.erl @@ -42,7 +42,6 @@ groups() -> [start_inets, start_httpc, start_httpd, - start_ftpc, start_tftpd ]}, {app_test, [], [app, appup]}]. @@ -298,44 +297,6 @@ start_httpd(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -start_ftpc(doc) -> - [{doc, "Start/stop of ftpc service"}]; -start_ftpc(Config0) when is_list(Config0) -> - process_flag(trap_exit, true), - ok = inets:start(), - case ftp_SUITE:init_per_suite(Config0) of - {skip, _} = Skip -> - Skip; - Config -> - FtpdHost = proplists:get_value(ftpd_host,Config), - {ok, Pid0} = inets:start(ftpc, [{host, FtpdHost}]), - Pids0 = [ServicePid || {_, ServicePid} <- - inets:services()], - true = lists:member(Pid0, Pids0), - [_|_] = inets:services_info(), - inets:stop(ftpc, Pid0), - ct:sleep(100), - Pids1 = [ServicePid || {_, ServicePid} <- - inets:services()], - false = lists:member(Pid0, Pids1), - {ok, Pid1} = - inets:start(ftpc, [{host, FtpdHost}], stand_alone), - Pids2 = [ServicePid || {_, ServicePid} <- - inets:services()], - false = lists:member(Pid1, Pids2), - ok = inets:stop(stand_alone, Pid1), - receive - {'EXIT', Pid1, shutdown} -> - ok - after 100 -> - ct:fail(stand_alone_not_shutdown) - end, - ok = inets:stop(), - catch ftp_SUITE:end_per_SUITE(Config) - end. - -%%------------------------------------------------------------------------- - start_tftpd() -> [{doc, "Start/stop of tfpd service"}]. start_tftpd(Config) when is_list(Config) -> diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl index 1e664337e6..d36d4dc53e 100644 --- a/lib/inets/test/inets_sup_SUITE.erl +++ b/lib/inets/test/inets_sup_SUITE.erl @@ -32,7 +32,7 @@ suite() -> ]. all() -> - [default_tree, ftpc_worker, tftpd_worker, + [default_tree, tftpd_worker, httpd_config, httpd_subtree, httpd_subtree_profile, httpc_subtree]. @@ -147,13 +147,11 @@ default_tree() -> "in the default case."}]. default_tree(Config) when is_list(Config) -> TopSupChildren = supervisor:which_children(inets_sup), - 4 = length(TopSupChildren), + 3 = length(TopSupChildren), {value, {httpd_sup, _, supervisor,[httpd_sup]}} = lists:keysearch(httpd_sup, 1, TopSupChildren), {value, {httpc_sup, _,supervisor,[httpc_sup]}} = lists:keysearch(httpc_sup, 1, TopSupChildren), - {value, {ftp_sup,_,supervisor,[ftp_sup]}} = - lists:keysearch(ftp_sup, 1, TopSupChildren), {value, {tftp_sup,_,supervisor,[tftp_sup]}} = lists:keysearch(tftp_sup, 1, TopSupChildren), @@ -163,8 +161,6 @@ default_tree(Config) when is_list(Config) -> {value, {httpc_handler_sup,_, supervisor, [httpc_handler_sup]}} = lists:keysearch(httpc_handler_sup, 1, HttpcSupChildren), - [] = supervisor:which_children(ftp_sup), - [] = supervisor:which_children(httpd_sup), %% Default profile @@ -177,30 +173,6 @@ default_tree(Config) when is_list(Config) -> ok. -ftpc_worker() -> - [{doc, "Makes sure the ftp worker processes are added and removed " - "appropriatly to/from the supervison tree."}]. -ftpc_worker(Config0) when is_list(Config0) -> - [] = supervisor:which_children(ftp_sup), - case ftp_SUITE:init_per_suite(Config0) of - {skip, _} = Skip -> - Skip; - Config -> - FtpdHost = proplists:get_value(ftpd_host,Config), - {ok, Pid} = inets:start(ftpc, [{host, FtpdHost}]), - case supervisor:which_children(ftp_sup) of - [{_,_, worker, [ftp]}] -> - inets:stop(ftpc, Pid), - ct:sleep(5000), - [] = supervisor:which_children(ftp_sup), - catch ftp_SUITE:end_per_SUITE(Config), - ok; - Children -> - catch ftp_SUITE:end_per_SUITE(Config), - exit({unexpected_children, Children}) - end - end. - tftpd_worker() -> [{doc, "Makes sure the tftp sub tree is correct."}]. tftpd_worker(Config) when is_list(Config) -> -- cgit v1.2.3