aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ftp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ftp')
-rw-r--r--lib/ftp/Makefile78
-rw-r--r--lib/ftp/doc/archive/rfc2428.txt451
-rw-r--r--lib/ftp/doc/archive/rfc2577.txt451
-rw-r--r--lib/ftp/doc/archive/rfc959.txt3933
-rw-r--r--lib/ftp/doc/html/.gitignore0
-rw-r--r--lib/ftp/doc/man3/.gitignore0
-rw-r--r--lib/ftp/doc/man6/.gitignore0
-rw-r--r--lib/ftp/doc/pdf/.gitignore0
-rw-r--r--lib/ftp/doc/src/Makefile154
-rw-r--r--lib/ftp/doc/src/book.xml49
-rw-r--r--lib/ftp/doc/src/ftp.xml948
-rw-r--r--lib/ftp/doc/src/ftp_client.xml84
-rw-r--r--lib/ftp/doc/src/introduction.xml46
-rw-r--r--lib/ftp/doc/src/notes.xml53
-rw-r--r--lib/ftp/doc/src/part.xml37
-rw-r--r--lib/ftp/doc/src/ref_man.xml36
-rw-r--r--lib/ftp/ebin/.gitignore0
-rw-r--r--lib/ftp/info2
-rw-r--r--lib/ftp/src/Makefile118
-rw-r--r--lib/ftp/src/ftp.app.src16
-rw-r--r--lib/ftp/src/ftp.erl2593
-rw-r--r--lib/ftp/src/ftp_app.erl47
-rw-r--r--lib/ftp/src/ftp_internal.hrl59
-rw-r--r--lib/ftp/src/ftp_progress.erl136
-rw-r--r--lib/ftp/src/ftp_response.erl203
-rw-r--r--lib/ftp/src/ftp_sup.erl68
-rw-r--r--lib/ftp/test/Makefile252
-rw-r--r--lib/ftp/test/erl_make_certs.erl475
-rw-r--r--lib/ftp/test/ftp.config1
-rw-r--r--lib/ftp/test/ftp.cover2
-rw-r--r--lib/ftp/test/ftp.spec1
-rw-r--r--lib/ftp/test/ftp_SUITE.erl1180
-rw-r--r--lib/ftp/test/ftp_SUITE_data/ftpd_hosts.skel18
-rw-r--r--lib/ftp/test/ftp_SUITE_data/vsftpd.conf26
-rw-r--r--lib/ftp/test/ftp_bench.spec1
-rw-r--r--lib/ftp/test/ftp_format_SUITE.erl328
l---------lib/ftp/test/ftp_internal.hrl1
-rw-r--r--lib/ftp/test/ftp_property_test_SUITE.erl53
-rw-r--r--lib/ftp/test/inets_test_lib.erl596
-rw-r--r--lib/ftp/test/inets_test_lib.hrl28
-rw-r--r--lib/ftp/test/property_test/README12
-rw-r--r--lib/ftp/test/property_test/ftp_simple_client_server.erl307
-rw-r--r--lib/ftp/test/property_test/ftp_simple_client_server_data/vsftpd.conf26
-rw-r--r--lib/ftp/vsn.mk24
44 files changed, 12893 insertions, 0 deletions
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<space><d><net-prt><d><net-addr><d><tcp-port><d>
+
+ The EPRT command keyword MUST be followed by a single space (ASCII
+ 32). Following the space, a delimiter character (<d>) 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 <net-prt> 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 <net-addr> 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 <tcp-port> 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:
+
+ <text stating that the network protocol is unsupported> \
+ (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:
+
+ <text indicating server is entering extended passive mode> \
+ (<d><d><d><tcp-port><d>)
+
+ 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<space><net-prt>
+
+ 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:
+
+ EPSV<space>ALL
+
+ 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
+ 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
+
+
+ Craig Metz
+ The Inner Net
+ Box 10314-1954
+ Blacksburg, VA 24062-0314
+
+ Phone: (DSN) 754-8590
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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
+
+
+
+ Shawn Ostermann
+ School of Electrical Engineering and Computer Science
+ Ohio University
+ 416 Morton Hall
+ Athens, OH 45701
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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 <CRLF> 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 <NL> 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., <CR>, <LF>, <NL>, <VT>, <FF>) which the printer
+ process will interpret appropriately. <CRLF>, 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 <CRLF>
+ 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 <CRLF>. 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
+
+
+ <CRLF> for ASCII, or <NL> 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 <CRLF> or <NL>,
+ 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). <SP> (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 <SP>
+ (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 <SP> (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 <byte size> - 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 <SP> R <SP>. 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 <CRLF> or <NL>. (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 <CRLF>. 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 <SP> (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
+
+
+ <SP>, 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 <SP>, 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 <SP> (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 <CRLF> (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 <SP> <username> <CRLF>
+ PASS <SP> <password> <CRLF>
+ ACCT <SP> <account-information> <CRLF>
+ CWD <SP> <pathname> <CRLF>
+ CDUP <CRLF>
+ SMNT <SP> <pathname> <CRLF>
+ QUIT <CRLF>
+ REIN <CRLF>
+ PORT <SP> <host-port> <CRLF>
+ PASV <CRLF>
+ TYPE <SP> <type-code> <CRLF>
+ STRU <SP> <structure-code> <CRLF>
+ MODE <SP> <mode-code> <CRLF>
+ RETR <SP> <pathname> <CRLF>
+ STOR <SP> <pathname> <CRLF>
+ STOU <CRLF>
+ APPE <SP> <pathname> <CRLF>
+ ALLO <SP> <decimal-integer>
+ [<SP> R <SP> <decimal-integer>] <CRLF>
+ REST <SP> <marker> <CRLF>
+ RNFR <SP> <pathname> <CRLF>
+ RNTO <SP> <pathname> <CRLF>
+ ABOR <CRLF>
+ DELE <SP> <pathname> <CRLF>
+ RMD <SP> <pathname> <CRLF>
+ MKD <SP> <pathname> <CRLF>
+ PWD <CRLF>
+ LIST [<SP> <pathname>] <CRLF>
+ NLST [<SP> <pathname>] <CRLF>
+ SITE <SP> <string> <CRLF>
+ SYST <CRLF>
+ STAT [<SP> <pathname>] <CRLF>
+ HELP [<SP> <string>] <CRLF>
+ NOOP <CRLF>
+
+
+
+
+
+
+
+
+
+
+
+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:
+
+ <username> ::= <string>
+ <password> ::= <string>
+ <account-information> ::= <string>
+ <string> ::= <char> | <char><string>
+ <char> ::= any of the 128 ASCII characters except <CR> and
+ <LF>
+ <marker> ::= <pr-string>
+ <pr-string> ::= <pr-char> | <pr-char><pr-string>
+ <pr-char> ::= printable characters, any
+ ASCII code 33 through 126
+ <byte-size> ::= <number>
+ <host-port> ::= <host-number>,<port-number>
+ <host-number> ::= <number>,<number>,<number>,<number>
+ <port-number> ::= <number>,<number>
+ <number> ::= any decimal integer 1 through 255
+ <form-code> ::= N | T | C
+ <type-code> ::= A [<sp> <form-code>]
+ | E [<sp> <form-code>]
+ | I
+ | L <sp> <byte-size>
+ <structure-code> ::= F | R | P
+ <mode-code> ::= S | B | C
+ <pathname> ::= <string>
+ <decimal-integer> ::= 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<CR> Connect to host S, port L,
+ establishing control connections.
+ <---- 220 Service ready <CRLF>.
+ username Doe <CR> USER Doe<CRLF>---->
+ <---- 331 User name ok,
+ need password<CRLF>.
+ password mumble <CR> PASS mumble<CRLF>---->
+ <---- 230 User logged in<CRLF>.
+ retrieve (local type) ASCII<CR>
+ (local pathname) test 1 <CR> User-FTP opens local file in ASCII.
+ (for. pathname) test.pl1<CR> RETR test.pl1<CRLF> ---->
+ <---- 150 File status okay;
+ about to open data
+ connection<CRLF>.
+ Server makes data connection
+ to port U.
+
+ <---- 226 Closing data connection,
+ file transfer successful<CRLF>.
+ type Image<CR> TYPE I<CRLF> ---->
+ <---- 200 Command OK<CRLF>
+ store (local type) image<CR>
+ (local pathname) file dump<CR> User-FTP opens local file in Image.
+ (for.pathname) >udd>cn>fd<CR> STOR >udd>cn>fd<CRLF> ---->
+ <---- 550 Access denied<CRLF>
+ terminate QUIT <CRLF> ---->
+ 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 "<abso.lute.path>" 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 <DFRANKLIN>, the new subdirectory
+ could only be referred to by the name <DFRANKLIN.MYDIR>.
+
+ 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<space>"<directory-name>"<space><commentary>
+
+ 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 <some.where>
+ 200 Working directory changed
+ MKD overrainbow
+ 257 "<some.where.overrainbow>" directory created
+ CWD overrainbow
+ 431 No such directory
+ CWD <some.where.overrainbow>
+ 200 Working directory changed
+
+ CWD <some.where>
+ 200 Working directory changed to <some.where>
+ MKD <unambiguous>
+ 257 "<unambiguous>" directory created
+ CWD <unambiguous>
+
+ 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
+
+
+ <unambiguous> 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 <overrainbow> 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
--- /dev/null
+++ b/lib/ftp/doc/html/.gitignore
diff --git a/lib/ftp/doc/man3/.gitignore b/lib/ftp/doc/man3/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ftp/doc/man3/.gitignore
diff --git a/lib/ftp/doc/man6/.gitignore b/lib/ftp/doc/man6/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ftp/doc/man6/.gitignore
diff --git a/lib/ftp/doc/pdf/.gitignore b/lib/ftp/doc/pdf/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ftp/doc/pdf/.gitignore
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE book SYSTEM "book.dtd">
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header titlestyle="normal">
+ <copyright>
+ <year>1997</year><year>2018</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>FTP</title>
+ <prepared>Péter Dimitrov</prepared>
+ <docno></docno>
+ <date>2018-02-26</date>
+ <rev>1.0</rev>
+ <file>book.sgml</file>
+ </header>
+ <insidecover>
+ </insidecover>
+ <pagetext>FTP</pagetext>
+ <preamble>
+ <contents level="2"></contents>
+ </preamble>
+ <parts lift="no">
+ <xi:include href="part.xml"/>
+ </parts>
+ <applications>
+ <xi:include href="ref_man.xml"/>
+ </applications>
+ <releasenotes>
+ <xi:include href="notes.xml"/>
+ </releasenotes>
+ <listofterms></listofterms>
+ <index></index>
+</book>
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1997</year><year>2016</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>ftp</title>
+ <prepared>Peter H&ouml;gfeldt</prepared>
+ <docno></docno>
+ <date>1997-11-05</date>
+ <rev>B</rev>
+ <file>ftp.xml</file>
+ </header>
+ <module>ftp</module>
+ <modulesummary>A File Transfer Protocol client.</modulesummary>
+
+ <description>
+
+ <p>This module implements a client for file transfer
+ according to a subset of the File Transfer Protocol (FTP), see
+ <url href="http://www.ietf.org/rfc/rfc959.txt">RFC 959</url>.</p>
+
+ <p>As from <c>Inets</c> 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 <seealso marker="#mode">mode</seealso>.</p>
+
+ <marker id="two_start"></marker>
+
+ <p>An FTP client can be started in two ways. One is using the
+ <seealso marker="#service_start">Inets service framework</seealso>,
+ the other is to start it directly as a standalone process
+ using function <seealso marker="#open">open</seealso>.</p>
+
+ <p>For a simple example of an FTP session, see
+ <seealso marker="ftp_client">Inets User's Guide</seealso>.</p>
+
+ <p>In addition to the ordinary functions for receiving and sending
+ files (see <c>recv/2</c>, <c>recv/3</c>, <c>send/2</c>, and
+ <c>send/3</c>) there are functions for receiving remote files as
+ binaries (see <c>recv_bin/2</c>) and for sending binaries to be
+ stored as remote files (see <c>send_bin/3</c>).</p>
+
+ <p>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 <c>send_chunk_start/2</c>, <c>send_chunk/2</c>, and
+ <c>send_chunk_end/1</c>. For receive, see
+ <c>recv_chunk_start/2</c> and <c>recv_chunk/</c>).</p>
+
+ <p>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 <c>ls</c> and <c>nlist</c>
+ varies. Often real errors are not reported as errors by <c>ls</c>,
+ even if, for example, a file or directory does not
+ exist. <c>nlist</c> 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.</p>
+
+ <marker id="service_start"></marker>
+ </description>
+
+ <section>
+ <title>FTP CLIENT SERVICE START/STOP</title>
+
+ <p>The FTP client can be started and stopped dynamically in runtime by
+ calling the <c>Inets</c> application API
+ <c>inets:start(ftpc, ServiceConfig)</c>,
+ or <c>inets:start(ftpc, ServiceConfig, How)</c>, and
+ <c>inets:stop(ftpc, Pid)</c>.
+ For details, see <seealso marker="inets">inets(3)</seealso>.</p>
+
+ <p>The available configuration options are as follows:</p>
+
+ <taglist>
+ <tag>{host, Host}</tag>
+ <item>
+ <marker id="host"></marker>
+ <p>Host = <c>string() | ip_address()</c></p>
+ </item>
+
+ <tag>{port, Port}</tag>
+ <item>
+ <marker id="port"></marker>
+ <p>Port = <c>integer() > 0</c></p>
+ <p>Default is <c>21</c>.</p>
+ </item>
+
+ <tag>{mode, Mode}</tag>
+ <item>
+ <marker id="mode"></marker>
+ <p>Mode = <c>active | passive</c></p>
+ <p>Default is <c>passive</c>.</p>
+ </item>
+
+ <tag>{verbose, Verbose}</tag>
+ <item>
+ <marker id="verbose"></marker>
+ <p>Verbose = <c>boolean()</c> </p>
+ <p>Determines if the FTP communication is to be
+ verbose or not.</p>
+ <p>Default is <c>false</c>.</p>
+ </item>
+
+ <tag>{debug, Debug}</tag>
+ <item>
+ <marker id="debug"></marker>
+ <p>Debug = <c>trace | debug | disable</c> </p>
+ <p>Debugging using the dbg toolkit. </p>
+ <p>Default is <c>disable</c>.</p>
+ </item>
+
+ <tag>{ipfamily, IpFamily}</tag>
+ <item>
+ <marker id="ipfamily"></marker>
+ <p>IpFamily = <c>inet | inet6 | inet6fb4</c> </p>
+ <p>With <c>inet6fb4</c> the client behaves as before, that is,
+ tries to use IPv6, and only if that does not work it
+ uses IPv4).</p>
+ <p>Default is <c>inet</c> (IPv4).</p>
+ </item>
+
+ <tag>{timeout, Timeout}</tag>
+ <item>
+ <marker id="timeout"></marker>
+ <p>Timeout = <c>non_neg_integer()</c></p>
+ <p>Connection time-out.</p>
+ <p>Default is <c>60000</c> (milliseconds).</p>
+ </item>
+
+ <tag>{dtimeout, DTimeout}</tag>
+ <item>
+ <marker id="dtimeout"></marker>
+ <p>DTimeout = <c>non_neg_integer() | infinity</c> </p>
+ <p>Data connect time-out.
+ The time the client waits for the server to connect to the
+ data socket.</p>
+ <p>Default is <c>infinity</c>. </p>
+ </item>
+
+ <tag>{progress, Progress}</tag>
+ <item>
+ <marker id="progress"></marker>
+ <p>Progress = <c>ignore | {CBModule, CBFunction, InitProgress}</c></p>
+ <p><c>CBModule = atom()</c>, <c>CBFunction = atom()</c></p>
+ <p><c>InitProgress = term()</c></p>
+ <p>Default is <c>ignore</c>.</p>
+ </item>
+
+ </taglist>
+
+ <p>Option <c>progress</c> 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 <c>ignore</c>,
+ that is, the option is not used. When the progress option is
+ specified, the following happens when <c>ftp:send/[3,4]</c> or
+ <c>ftp:recv/[3,4]</c> are called:</p>
+
+ <list type="bulleted">
+ <item>
+ <p>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 <c>UserProgressTerm</c> that will
+ be used as input the next time the callback function is
+ called.</p>
+ <p><c>
+ CBModule:CBFunction(InitProgress, File, {file_size, FileSize})
+ </c></p>
+ </item>
+
+ <item>
+ <p>Every time a chunk of bytes is transferred the
+ following call is made:</p>
+ <p><c>
+ CBModule:CBFunction(UserProgressTerm, File, {transfer_size, TransferSize})
+ </c></p>
+ </item>
+
+ <item>
+ <p>At the end of the file the following call is
+ made to indicate the end of the transfer:</p>
+ <p><c>
+ CBModule:CBFunction(UserProgressTerm, File, {transfer_size, 0})
+ </c></p>
+ </item>
+ </list>
+
+ <p>The callback function is to be defined as follows:</p>
+
+ <p><c>
+ CBModule:CBFunction(UserProgressTerm, File, Size) -> UserProgressTerm
+ </c></p>
+
+ <p><c>
+ CBModule = CBFunction = atom()
+ </c></p>
+
+ <p><c>
+ UserProgressTerm = term()
+ </c></p>
+
+ <p><c>
+ File = string()
+ </c></p>
+
+ <p><c>
+ Size = {transfer_size, integer()} | {file_size, integer()} | {file_size, unknown}
+ </c></p>
+
+ <p>For remote files, <c>ftp</c> cannot determine the
+ file size in a platform independent way. In this case the size
+ becomes <c>unknown</c> and it is left to the application to
+ determine the size.</p>
+
+ <note>
+ <p>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 <c>ignore</c>.</p>
+ </note>
+
+ <p>The file transfer type is set to the default of the FTP server
+ when the session is opened. This is usually ASCCI mode.
+ </p>
+
+ <p>The current local working directory (compare <c>lpwd/1</c>) is set
+ to the value reported by <c>file:get_cwd/1</c>, the wanted
+ local directory.
+ </p>
+
+ <p>The return value <c>Pid</c> 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.</p>
+ </section>
+
+ <section>
+ <title>DATA TYPES</title>
+ <p>The following type definitions are used by more than one
+ function in the FTP client API:</p>
+ <p><c>pid()</c> = identifier of an FTP connection</p>
+ <p><c>string()</c> = list of ASCII characters</p>
+ <p><c>shortage_reason()</c> = <c>etnospc | epnospc</c></p>
+ <p><c>restriction_reason()</c> = <c>epath | efnamena | elogin | enotbinary</c>
+ - all restrictions are not always relevant to all functions
+ </p>
+ <p><c>common_reason()</c> = <c>econn | eclosed | term()</c>
+ - some explanation of what went wrong</p>
+
+ <marker id="account"></marker>
+ </section>
+
+ <funcs>
+ <func>
+ <name>account(Pid, Account) -> ok | {error, Reason}</name>
+ <fsummary>Specifies which account to use.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Account = string()</v>
+ <v>Reason = eacct | common_reason()</v>
+ </type>
+ <desc>
+ <p>Sets the account for an operation, if needed.</p>
+
+ <marker id="append"></marker>
+ <marker id="append2"></marker>
+ <marker id="append3"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>append(Pid, LocalFile) -> </name>
+ <name>append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+ <fsummary>Transfers a file to remote server, and appends it to
+ <c>Remotefile</c>.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>LocalFile = RemoteFile = string()</v>
+ <v>Reason = epath | elogin | etnospc | epnospc | efnamena | common_reason</v>
+ </type>
+ <desc>
+ <p>Transfers the file <c>LocalFile</c> to the remote server. If
+ <c>RemoteFile</c> is specified, the name of the remote file that the
+ file is appended to is set to <c>RemoteFile</c>, otherwise
+ to <c>LocalFile</c>. If the file does not exists,
+ it is created.</p>
+
+ <marker id="append_bin"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <fsummary>Transfers a binary into a remote file.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Bin = binary()()</v>
+ <v>RemoteFile = string()</v>
+ <v>Reason = restriction_reason()| shortage_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Transfers the binary <c>Bin</c> to the remote server and appends
+ it to the file <c>RemoteFile</c>. If the file does not exist, it
+ is created.</p>
+
+ <marker id="append_chunk"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>append_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <fsummary>Appends a chunk to the remote file.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Bin = binary()</v>
+ <v>Reason = echunk | restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Transfers the chunk <c>Bin</c> to the remote server, which
+ appends it to the file specified in the call to
+ <c>append_chunk_start/2</c>.</p>
+ <p>For some errors, for example, file system full, it is
+ necessary to call <c>append_chunk_end</c> to get the
+ proper reason.</p>
+
+ <marker id="append_chunk_start"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>append_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <fsummary>Starts transfer of file chunks for appending to <c>File</c>.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>File = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Starts the transfer of chunks for appending to the file
+ <c>File</c> at the remote server. If the file does not exist,
+ it is created.</p>
+
+ <marker id="append_chunk_end"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>append_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <fsummary>Stops transfer of chunks for appending.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Reason = echunk | restriction_reason() | shortage_reason() </v>
+ </type>
+ <desc>
+ <p>Stops transfer of chunks for appending to the remote server.
+ The file at the remote server, specified in the call to
+ <c>append_chunk_start/2</c>, is closed by the server.</p>
+
+ <marker id="cd"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>cd(Pid, Dir) -> ok | {error, Reason}</name>
+ <fsummary>Changes remote working directory.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Dir = string()</v>
+ <v>Reason = restriction_reason() | common_reason() </v>
+ </type>
+ <desc>
+ <p>Changes the working directory at the remote server to
+ <c>Dir</c>.</p>
+
+ <marker id="close"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>close(Pid) -> ok</name>
+ <fsummary>Ends the FTP session.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Ends an FTP session, created using function
+ <seealso marker="#open">open</seealso>.</p>
+
+ <marker id="delete"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>delete(Pid, File) -> ok | {error, Reason}</name>
+ <fsummary>Deletes a file at the remote server.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>File = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Deletes the file <c>File</c> at the remote server.</p>
+
+ <marker id="append"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>formaterror(Tag) -> string()</name>
+ <fsummary>Returns error diagnostics.</fsummary>
+ <type>
+ <v>Tag = {error, atom()} | atom()</v>
+ </type>
+ <desc>
+ <p>Given an error return value <c>{error, AtomReason}</c>,
+ this function returns a readable string describing the error.</p>
+
+ <marker id="lcd"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>lcd(Pid, Dir) -> ok | {error, Reason}</name>
+ <fsummary>Changes local working directory.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Dir = string()</v>
+ <v>Reason = restriction_reason()</v>
+ </type>
+ <desc>
+ <p>Changes the working directory to <c>Dir</c> for the local client.</p>
+
+ <marker id="lpwd"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>lpwd(Pid) -> {ok, Dir}</name>
+ <fsummary>Gets local current working directory.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ </type>
+ <desc>
+ <p>Returns the current working directory at the local client.</p>
+
+ <marker id="ls"></marker>
+ <marker id="ls1"></marker>
+ <marker id="ls2"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>ls(Pid) -> </name>
+ <name>ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <fsummary>List of files.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Pathname = string()</v>
+ <v>Listing = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Returns a list of files in long format.</p>
+ <p><c>Pathname</c> can be a directory, a group of files, or
+ a file. The <c>Pathname</c> string can contain wildcards.</p>
+ <p><c>ls/1</c> implies the current remote directory of the user.</p>
+ <p>The format of <c>Listing</c> depends on the operating system.
+ On UNIX, it is typically produced from the output of the
+ <c>ls -l</c> shell command.</p>
+
+ <marker id="mkdir"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>mkdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <fsummary>Creates a remote directory.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Dir = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Creates the directory <c>Dir</c> at the remote server.</p>
+
+ <marker id="nlist"></marker>
+ <marker id="nlist1"></marker>
+ <marker id="nlist2"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>nlist(Pid) -> </name>
+ <name>nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name>
+ <fsummary>List of files.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Pathname = string()</v>
+ <v>Listing = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Returns a list of files in short format.</p>
+ <p><c>Pathname</c> can be a directory, a group of files, or
+ a file. The <c>Pathname</c> string can contain wildcards.</p>
+ <p><c>nlist/1</c> implies the current remote directory of the user.</p>
+ <p>The format of <c>Listing</c> is a stream of
+ filenames where each filename is separated by &lt;CRLF&gt; or
+ &lt;NL&gt;. Contrary to function <c>ls</c>, the purpose of
+ <c>nlist</c> is to enable a program to
+ process filename information automatically.</p>
+
+ <marker id="open"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>open(Host) -> {ok, Pid} | {error, Reason}</name>
+ <name>open(Host, Opts) -> {ok, Pid} | {error, Reason}</name>
+ <fsummary>Starts a standalone FTP client.</fsummary>
+ <type>
+ <v>Host = string() | ip_address()</v>
+ <v>Opts = options()</v>
+ <v>options() = [option()]</v>
+ <v>option() = start_option() | open_option()</v>
+ <v>start_option() = {verbose, verbose()} | {debug, debug()}</v>
+ <v>verbose() = boolean() (default is false)</v>
+ <v>debug() = disable | debug | trace (default is disable)</v>
+ <v>open_option() = {ipfamily, ipfamily()} | {port, port()} | {mode, mode()} | {tls, tls_options()} | {timeout, timeout()} | {dtimeout, dtimeout()} | {progress, progress()}</v>
+ <v>ipfamily() = inet | inet6 | inet6fb4 (default is inet)</v>
+ <v>port() = integer() > 0 (default is 21)</v>
+ <v>mode() = active | passive (default is passive)</v>
+ <v>tls_options() = [<seealso marker="ssl:ssl#type-ssloption">ssl:ssloption()</seealso>]</v>
+ <v>timeout() = integer() > 0 (default is 60000 milliseconds)</v>
+ <v>dtimeout() = integer() > 0 | infinity (default is infinity)</v>
+ <v>pogress() = ignore | {module(), function(), initial_data()} (default is ignore)</v>
+ <v>module() = atom()</v>
+ <v>function() = atom()</v>
+ <v>initial_data() = term()</v>
+ <v>Reason = ehost | term()</v>
+ </type>
+
+ <desc>
+ <p>Starts a standalone FTP client process
+ (without the <c>Inets</c> service framework) and
+ opens a session with the FTP server at <c>Host</c>. </p>
+
+ <p>If option <c>{tls, tls_options()}</c> is present, the FTP session
+ is transported over <c>tls</c> (<c>ftps</c>, see
+ <url href="http://www.ietf.org/rfc/rfc4217.txt">RFC 4217</url>).
+ The list <c>tls_options()</c> can be empty. The function
+ <seealso marker="ssl:ssl#connect/3"><c>ssl:connect/3</c></seealso>
+ is used for securing both the control connection and the data sessions.
+ </p>
+
+ <p>A session opened in this way is closed using function
+ <seealso marker="#close">close</seealso>.</p>
+
+ <marker id="pwd"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>pwd(Pid) -> {ok, Dir} | {error, Reason}</name>
+ <fsummary>Gets the remote current working directory.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Returns the current working directory at the remote server.</p>
+
+ <marker id="recv"></marker>
+ <marker id="recv2"></marker>
+ <marker id="recv3"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>recv(Pid, RemoteFile) -> </name>
+ <name>recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name>
+ <fsummary>Transfers a file from remote server.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>RemoteFile = LocalFile = string()</v>
+ <v>Reason = restriction_reason() | common_reason() | file_write_error_reason() </v>
+ <v>file_write_error_reason() = see file:write/2</v>
+ </type>
+ <desc>
+ <p>Transfers the file <c>RemoteFile</c> from the remote server
+ to the file system of the local client. If
+ <c>LocalFile</c> is specified, the local file will be
+ <c>LocalFile</c>, otherwise
+ <c>RemoteFile</c>.</p>
+ <p>If the file write fails (for example, <c>enospc</c>), the command is
+ aborted and <c>{error, file_write_error_reason()}</c> is returned.
+ However, the file is <em>not</em> removed.</p>
+
+ <marker id="recv_bin"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name>
+ <fsummary>Transfers a file from remote server as a binary.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Bin = binary()</v>
+ <v>RemoteFile = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Transfers the file <c>RemoteFile</c> from the remote server and
+ receives it as a binary.</p>
+
+ <marker id="recv_chunk_start"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name>
+ <fsummary>Starts chunk-reading of the remote file.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>RemoteFile = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Starts transfer of the file <c>RemoteFile</c> from the
+ remote server.</p>
+
+ <marker id="recv_chunk"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name>
+ <fsummary>Receives a chunk of the remote file.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Bin = binary()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Receives a chunk of the remote file (<c>RemoteFile</c> of
+ <c>recv_chunk_start</c>). The return values have the following
+ meaning:</p>
+ <list type="bulleted">
+ <item><c>ok</c> = the transfer is complete.</item>
+ <item><c>{ok, Bin}</c> = just another chunk of the file.</item>
+ <item><c>{error, Reason}</c> = transfer failed.</item>
+ </list>
+
+ <marker id="rename"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>rename(Pid, Old, New) -> ok | {error, Reason}</name>
+ <fsummary>Renames a file at the remote server.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>CurrFile = NewFile = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Renames <c>Old</c> to <c>New</c> at the remote server.</p>
+
+ <marker id="rmdir"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>rmdir(Pid, Dir) -> ok | {error, Reason}</name>
+ <fsummary>Removes a remote directory.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Dir = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Removes directory <c>Dir</c> at the remote server.</p>
+
+ <marker id="send"></marker>
+ <marker id="send2"></marker>
+ <marker id="send3"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>send(Pid, LocalFile) -></name>
+ <name>send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name>
+ <fsummary>Transfers a file to the remote server.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>LocalFile = RemoteFile = string()</v>
+ <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
+ </type>
+ <desc>
+ <p>Transfers the file <c>LocalFile</c> to the remote server. If
+ <c>RemoteFile</c> is specified, the name of the remote file is set
+ to <c>RemoteFile</c>, otherwise to <c>LocalFile</c>.</p>
+
+ <marker id="send_bin"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name>
+ <fsummary>Transfers a binary into a remote file.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Bin = binary()()</v>
+ <v>RemoteFile = string()</v>
+ <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
+ </type>
+ <desc>
+ <p>Transfers the binary <c>Bin</c> into the file <c>RemoteFile</c>
+ at the remote server.</p>
+
+ <marker id="send_chunk"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>send_chunk(Pid, Bin) -> ok | {error, Reason}</name>
+ <fsummary>Writes a chunk to the remote file.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Bin = binary()</v>
+ <v>Reason = echunk | restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Transfers the chunk <c>Bin</c> to the remote server, which
+ writes it into the file specified in the call to
+ <c>send_chunk_start/2</c>.</p>
+ <p>For some errors, for example, file system full, it is
+ necessary to to call <c>send_chunk_end</c> to get the
+ proper reason.</p>
+
+ <marker id="send_chunk_start"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>send_chunk_start(Pid, File) -> ok | {error, Reason}</name>
+ <fsummary>Starts transfer of file chunks.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>File = string()</v>
+ <v>Reason = restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Starts transfer of chunks into the file <c>File</c> at the
+ remote server.</p>
+
+ <marker id="send_chunk_end"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>send_chunk_end(Pid) -> ok | {error, Reason}</name>
+ <fsummary>Stops transfer of chunks.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Reason = restriction_reason() | common_reason() | shortage_reason()</v>
+ </type>
+ <desc>
+ <p>Stops transfer of chunks to the remote server. The file at the
+ remote server, specified in the call to <c>send_chunk_start/2</c>
+ is closed by the server.</p>
+
+ <marker id="type"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>type(Pid, Type) -> ok | {error, Reason}</name>
+ <fsummary>Sets transfer type to <c>ascii</c>or <c>binary</c>.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Type = ascii | binary</v>
+ <v>Reason = etype | restriction_reason() | common_reason()</v>
+ </type>
+ <desc>
+ <p>Sets the file transfer type to <c>ascii</c> or <c>binary</c>. When
+ an FTP session is opened, the default transfer type of the
+ server is used, most often <c>ascii</c>, which is default
+ according to <url href="http://www.ietf.org/rfc/rfc959.txt">RFC 959</url>.</p>
+ <marker id="user3"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>user(Pid, User, Password) -> ok | {error, Reason}</name>
+ <fsummary>User login.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>User = Password = string()</v>
+ <v>Reason = euser | common_reason()</v>
+ </type>
+ <desc>
+ <p>Performs login of <c>User</c> with <c>Password</c>.</p>
+
+ <marker id="user4"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>user(Pid, User, Password, Account) -> ok | {error, Reason}</name>
+ <fsummary>User login.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>User = Password = string()</v>
+ <v>Reason = euser | common_reason() </v>
+ </type>
+ <desc>
+ <p>Performs login of <c>User</c> with <c>Password</c> to the account
+ specified by <c>Account</c>.</p>
+
+ <marker id="quote"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>quote(Pid, Command) -> [FTPLine]</name>
+ <fsummary>Sends an arbitrary FTP command.</fsummary>
+ <type>
+ <v>Pid = pid()</v>
+ <v>Command = string()</v>
+ <v>FTPLine = string(</v>
+ </type>
+ <desc><note><p>The telnet end of line characters, from the FTP
+ protocol definition, CRLF, for example, "\\r\\n" has been removed.</p></note>
+ <p>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.</p>
+ <note>
+ <p>FTP commands requiring a data connection cannot be
+ successfully issued with this function.</p>
+ </note>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>ERRORS</title>
+ <p>The possible error reasons and the corresponding diagnostic strings
+ returned by <c>formaterror/1</c> are as follows:
+ </p>
+ <taglist>
+ <tag><c>echunk</c></tag>
+ <item>
+ <p>Synchronization error during chunk sending according to one
+ of the following:
+ </p><list type="bulleted">
+ <item>A call is made to <c>send_chunk/2</c> or <c>send_chunk_end/1</c>
+ before a call to <c>send_chunk_start/2</c>.</item>
+ <item>A call has been made to another transfer function during chunk
+ sending, that is, before a call to <c>send_chunk_end/1</c>.</item>
+ </list>
+ </item>
+ <tag><c>eclosed</c></tag>
+ <item>
+ <p>The session is closed.</p>
+ </item>
+ <tag><c>econn</c></tag>
+ <item>
+ <p>Connection to the remote server is prematurely closed.</p>
+ </item>
+ <tag><c>ehost</c></tag>
+ <item>
+ <p>Host is not found, FTP server is not found, or connection is rejected
+ by FTP server.</p>
+ </item>
+ <tag><c>elogin</c></tag>
+ <item>
+ <p>User is not logged in.</p>
+ </item>
+ <tag><c>enotbinary</c></tag>
+ <item>
+ <p>Term is not a binary.</p>
+ </item>
+ <tag><c>epath</c></tag>
+ <item>
+ <p>No such file or directory, or directory already exists, or
+ permission denied.</p>
+ </item>
+ <tag><c>etype</c></tag>
+ <item>
+ <p>No such type.</p>
+ </item>
+ <tag><c>euser</c></tag>
+ <item>
+ <p>Invalid username or password.</p>
+ </item>
+ <tag><c>etnospc</c></tag>
+ <item>
+ <p>Insufficient storage space in system [452].</p>
+ </item>
+ <tag><c>epnospc</c></tag>
+ <item>
+ <p>Exceeded storage allocation (for current directory or
+ dataset) [552].</p>
+ </item>
+ <tag><c>efnamena</c></tag>
+ <item>
+ <p>Filename not allowed [553].</p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p><seealso marker="kernel:file">file(3)</seealso>
+ <seealso marker="stdlib:filename">filename(3)</seealso>
+ and J. Postel and J. Reynolds: File Transfer Protocol
+ (<url href="http://www.ietf.org/rfc/rfc959.txt">RFC 959</url>).
+ </p>
+ </section>
+
+</erlref>
+
+
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2004</year><year>2016</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>FTP Client</title>
+ <prepared>Ingela Anderton Andin</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date></date>
+ <rev></rev>
+ <file>ftp_client.xml</file>
+ </header>
+
+ <section>
+ <title>Getting Started</title>
+
+ <p>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.</p>
+
+ <p>The client supports IPv6 as long as the underlying mechanisms
+ also do so.</p>
+
+ <p>The following is a simple example of an FTP session, where
+ the user <c>guest</c> with password <c>password</c> logs on to
+ the remote host <c>erlang.org</c>:</p>
+ <code type="erl"><![CDATA[
+ 1> 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
+ ]]></code>
+ <p> The file
+ <c>appl.erl</c> is transferred from the remote to the local
+ host. When the session is opened, the current directory at
+ the remote host is <c>/home/guest</c>, and <c>/home/fred</c>
+ at the local host. Before transferring the file, the current
+ local directory is changed to <c>/home/eproj/examples</c>, and
+ the remote directory is set to
+ <c>/home/guest/appl/examples</c>.</p>
+ </section>
+</chapter>
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>1997</year><year>2018</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Introduction</title>
+ <prepared>Péter Dimitrov</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2018-02-26</date>
+ <rev>A</rev>
+ <file>introduction.xml</file>
+ </header>
+
+ <section>
+ <title>Purpose</title>
+ <p>An <c>FTP</c> client.</p>
+ </section>
+
+ <section>
+ <title>Prerequisites</title>
+ <p>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.</p>
+ </section>
+</chapter>
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2002</year><year>2018</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>FTP Release Notes</title>
+ <prepared></prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2018-02-26</date>
+ <rev>A</rev>
+ <file>notes.xml</file>
+ </header>
+
+ <section><title>FTP 1.0.0</title>
+
+ <section><title>First released version</title>
+ <list>
+ <item>
+ <p>
+ 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.</p>
+ <p>
+ Own Id: OTP-14113</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+</chapter>
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>2004</year><year>2018</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Inets User's Guide</title>
+ <prepared>Péter Dimitrov</prepared>
+ <docno></docno>
+ <date>2018-02-26</date>
+ <rev>A</rev>
+ <file>part.sgml</file>
+ </header>
+ <description>
+ <p>The <c>FTP</c> application provides an FTP client.</p>
+ </description>
+ <xi:include href="introduction.xml"/>
+ <xi:include href="ftp_client.xml"/>
+</part>
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 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE application SYSTEM "application.dtd">
+
+<application xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>1997</year><year>2018</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Inets Reference Manual</title>
+ <prepared>Péter Dimitrov</prepared>
+ <docno></docno>
+ <date>2018-02-26</date>
+ <rev>1.0</rev>
+ <file>ref_man.xml</file>
+ </header>
+ <description>
+ <p>An <c>FTP</c> client.</p>
+ </description>
+ <xi:include href="ftp.xml"/>
+</application>
diff --git a/lib/ftp/ebin/.gitignore b/lib/ftp/ebin/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ftp/ebin/.gitignore
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, <Port>, <Flags>) -> {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()}.
+
+%% <BACKWARD-COMPATIBILLITY>
+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;
+%% </BACKWARD-COMPATIBILLITY>
+
+open(Host) ->
+ open(Host, []).
+
+-spec open(Host :: string() | inet:ip_address(), Opts :: list()) ->
+ {'ok', Pid :: pid()} | {'error', Reason :: 'ehost' | term()}.
+
+%% <BACKWARD-COMPATIBILLITY>
+open(Host, Port) when is_integer(Port) ->
+ open(Host, [{port, Port}]);
+%% </BACKWARD-COMPATIBILLITY>
+
+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, <Acc>) -> 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, <Dir>) -> 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 = <<?CR,?LF>>}};
+ 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,<<CtrlData/binary, Data/binary>>,State]),
+ case ftp_response:parse_lines(<<CtrlData/binary, Data/binary>>,
+ 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}};
+ _ ->
+ %% <WTF>
+ %% 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
+ %% </WTF>
+ 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
+%% <SP>, 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 <SP> (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(<<C1, C2, C3, $-, Rest/binary>>, Lines, start) ->
+ parse_lines(Rest, [$-, C3, C2, C1 | Lines], {C1, C2, C3});
+%% Only one line exists
+parse_lines(<<C1, C2, C3, ?WHITE_SPACE, Bin/binary>>, Lines, start) ->
+ parse_lines(Bin, [?WHITE_SPACE, C3, C2, C1 | Lines], finish);
+
+%% Last line found
+parse_lines(<<?CR, ?LF, C1, C2, C3, ?WHITE_SPACE, Rest/binary>>, 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(<<?CR, ?LF, C1, C2, C3>> = Bin, Lines, {C1, C2, C3}) ->
+ {continue, {Bin, Lines, {C1, C2, C3}}};
+%% Intermidate line begining with status code
+parse_lines(<<?CR, ?LF, C1, C2, C3, Rest/binary>>, 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(<<?CR, ?LF, C1, C2>> = Data, Lines, {C1, C2, _} = StatusCode) ->
+ {continue, {Data, Lines, StatusCode}};
+parse_lines(<<?CR, ?LF, C1>> = Data, Lines, {C1, _, _} = StatusCode) ->
+ {continue, {Data, Lines, StatusCode}};
+parse_lines(<<?CR, ?LF>> = Data, Lines, {_,_,_} = StatusCode) ->
+ {continue, {Data, Lines, StatusCode}};
+parse_lines(<<?LF>> = Data, Lines, {_,_,_} = StatusCode) ->
+ {continue, {Data, Lines, StatusCode}};
+parse_lines(<<>> = Data, Lines, {_,_,_} = StatusCode) ->
+ {continue, {Data, Lines, StatusCode}};
+%% Part of the multiple lines
+parse_lines(<<Octet, Rest/binary>>, Lines, {_,_, _} = StatusCode) ->
+ parse_lines(Rest, [Octet | Lines], StatusCode);
+
+%% End of FTP server response found
+parse_lines(<<?CR, ?LF>>, Lines, finish) ->
+ {ok, lists:reverse([?LF, ?CR | Lines]), <<>>};
+parse_lines(<<?CR, ?LF, Rest/binary>>, Lines, finish) ->
+ {ok, lists:reverse([?LF, ?CR | Lines]), Rest};
+
+%% Potential end found wait for more data
+parse_lines(<<?CR>> = Data, Lines, finish) ->
+ {continue, {Data, Lines, finish}};
+parse_lines(<<>> = Data, Lines, finish) ->
+ {continue, {Data, Lines, finish}};
+%% Part of last line
+parse_lines(<<Octet, Rest/binary>>, 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, <<Contents1/binary,Contents2/binary>>, 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, <<Contents/binary,Contents/binary>>, 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, <<Contents/binary,Contents/binary>>, 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, <<Acc/binary, Bin/binary>>, 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<C,C=<255 ->
+ 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<C,C=<255 ->
+ 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<C,C=<255 ->
+ 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(<<H,T1/binary>>, <<H,T2/binary>>, 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('[email protected]').
+
+-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("<ERROR> Failed starting application: "
+ "~n Error1: ~p", [Error1]),
+ tsf({failed_starting_inets, Error1})
+ end;
+ Error2 ->
+ tsp("<ERROR> 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("<ERROR> 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("<ERROR> 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, "</html>") of
+ 0 ->
+ case string:rstr(Body, "</HTML>") 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","[email protected]"}};
+ 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)"