Index: /trunk/include/iprt/err.h
===================================================================
--- /trunk/include/iprt/err.h	(revision 82664)
+++ /trunk/include/iprt/err.h	(revision 82665)
@@ -2710,4 +2710,12 @@
 /** @} */
 
+/** @name FTP status codes
+ * @{ */
+/** FTP Internal Server Error. */
+#define VERR_FTP_STATUS_SERVER_ERROR                            (-26400)
+/** FTP initialization failed. */
+#define VERR_FTP_INIT_FAILED                                    (-26401)
+/** @} */
+
 /* SED-END */
 
Index: /trunk/include/iprt/ftp.h
===================================================================
--- /trunk/include/iprt/ftp.h	(revision 82665)
+++ /trunk/include/iprt/ftp.h	(revision 82665)
@@ -0,0 +1,103 @@
+/* $Id$ */
+/** @file
+ * Header file for FTP client / server implementations.
+ */
+
+/*
+ * Copyright (C) 2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef IPRT_INCLUDED_ftp_h
+#define IPRT_INCLUDED_ftp_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+
+RT_C_DECLS_BEGIN
+
+/** @defgroup grp_rt_ftp   RTFTPServer - FTP server implementation.
+ * @ingroup grp_rt
+ * @{
+ */
+
+/** @todo the following three definitions may move the iprt/types.h later. */
+/** FTP server handle. */
+typedef R3PTRTYPE(struct RTFTPSERVERINTERNAL *) RTFTPSERVER;
+/** Pointer to a FTP server handle. */
+typedef RTFTPSERVER                            *PRTFTPSERVER;
+/** Nil FTP client handle. */
+#define NIL_RTFTPSERVER                         ((RTFTPSERVER)0)
+
+/**
+ * Enumeration for FTP server reply codes.
+ *
+ ** @todo Might needs more codes, not complete yet.
+ */
+typedef enum RTFTPSERVER_REPLY
+{
+    /** Invalid reply type, do not use. */
+    RTFTPSERVER_REPLY_INVALID                        = 0,
+    /** Command not implemented, superfluous at this site. */
+    RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL_SUPERFLUOUS = 202,
+    /** Command okay. */
+    RTFTPSERVER_REPLY_OKAY                           = 200,
+    /** Service ready for new user. */
+    RTFTPSERVER_REPLY_READY_FOR_NEW_USER             = 220,
+    /** Closing data connection. */
+    RTFTPSERVER_REPLY_CLOSING_DATA_CONN              = 226,
+    /** User logged in, proceed. */
+    RTFTPSERVER_REPLY_LOGGED_IN_PROCEED              = 230,
+    /** User name okay, need password. */
+    RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD    = 331,
+    /** Connection closed; transfer aborted. */
+    RTFTPSERVER_REPLY_CONN_CLOSED_TRANSFER_ABORTED   = 426,
+    /** Syntax error, command unrecognized. */
+    RTFTPSERVER_REPLY_ERROR_CMD_NOT_RECOGNIZED       = 500,
+    /** Syntax error in parameters or arguments. */
+    RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS       = 501,
+    /** Command not implemented. */
+    RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL             = 502,
+    /** Bad sequence of commands. */
+    RTFTPSERVER_REPLY_ERROR_BAD_SEQUENCE             = 503,
+    /** Command not implemented for that parameter. */
+    RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL_PARAM       = 504,
+    /** Not logged in. */
+    RTFTPSERVER_REPLY_NOT_LOGGED_IN                  = 530,
+    /** The usual 32-bit hack. */
+    RTFTPSERVER_REPLY_32BIT_HACK                     = 0x7fffffff
+} RTFTPSERVER_REPLY;
+
+/**
+ * Creates a FTP server instance.
+ *
+ * @returns IPRT status code.
+ * @param   phFTPServer         Where to store the FTP server handle.
+ * @param   pcszAddress         The address for creating a listening socket.
+ *                              If NULL or empty string the server is bound to all interfaces.
+ * @param   uPort               The port for creating a listening socket.
+ * @param   pcszPathRoot        Root path of the FTP server serving.
+ */
+RTR3DECL(int) RTFTPServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
+                                const char *pcszPathRoot);
+
+/**
+ * Destroys a FTP server instance.
+ *
+ * @returns IPRT status code.
+ * @param   hFTPServer          Handle to the FTP server handle.
+ */
+RTR3DECL(int) RTFTPServerDestroy(RTFTPSERVER hFTPServer);
+
+RT_C_DECLS_END
+
+#endif /* !IPRT_INCLUDED_ftp_h */
Index: /trunk/include/iprt/log.h
===================================================================
--- /trunk/include/iprt/log.h	(revision 82664)
+++ /trunk/include/iprt/log.h	(revision 82665)
@@ -4,5 +4,5 @@
 
 /*
- * Copyright (C) 2006-2019 Oracle Corporation
+ * Copyright (C) 2006-2020 Oracle Corporation
  *
  * This file is part of VirtualBox Open Source Edition (OSE), as
@@ -62,4 +62,5 @@
     RTLOGGROUP_FILE,
     RTLOGGROUP_FS,
+    RTLOGGROUP_FTP,
     RTLOGGROUP_HTTP,
     RTLOGGROUP_IOQUEUE,
@@ -90,4 +91,7 @@
  *         If anyone might be wondering what the alphabet looks like:
  *              a b c d e f g h i j k l m n o p q r s t u v w x y z
+ *
+ * The RT_XX log group names are placeholders for new modules being added,
+ * to make sure that there always is a total of 32 log group entries.
  */
 #define RT_LOGGROUP_NAMES \
@@ -99,4 +103,5 @@
     "RT_FILE", \
     "RT_FS", \
+    "RT_FTP", \
     "RT_HTTP", \
     "RT_IOQUEUE", \
@@ -111,5 +116,4 @@
     "RT_TIMER", \
     "RT_VFS", \
-    "RT_19", \
     "RT_20", \
     "RT_21", \
@@ -123,5 +127,5 @@
     "RT_29", \
     "RT_30", \
-    "RT_ZIP"  \
+    "RT_ZIP"
 
 
Index: /trunk/include/iprt/mangling.h
===================================================================
--- /trunk/include/iprt/mangling.h	(revision 82664)
+++ /trunk/include/iprt/mangling.h	(revision 82665)
@@ -11,5 +11,5 @@
 
 /*
- * Copyright (C) 2011-2019 Oracle Corporation
+ * Copyright (C) 2011-2020 Oracle Corporation
  *
  * This file is part of VirtualBox Open Source Edition (OSE), as
@@ -1035,4 +1035,6 @@
 # define RTFsIsoMakerCmdEx                              RT_MANGLER(RTFsIsoMakerCmdEx)
 # define RTFsNtfsVolOpen                                RT_MANGLER(RTFsNtfsVolOpen)
+# define RTFTPServerCreate                              RT_MANGLER(RTFTPServerCreate)
+# define RTFTPServerDestroy                             RT_MANGLER(RTFTPServerDestroy)
 # define RTFuzzCmdMaster                                RT_MANGLER(RTFuzzCmdMaster)
 # define RTFuzzCtxCfgGetBehavioralFlags                 RT_MANGLER(RTFuzzCtxCfgGetBehavioralFlags)
Index: /trunk/src/VBox/Runtime/Makefile.kmk
===================================================================
--- /trunk/src/VBox/Runtime/Makefile.kmk	(revision 82664)
+++ /trunk/src/VBox/Runtime/Makefile.kmk	(revision 82665)
@@ -5,5 +5,5 @@
 
 #
-# Copyright (C) 2006-2019 Oracle Corporation
+# Copyright (C) 2006-2020 Oracle Corporation
 #
 # This file is part of VirtualBox Open Source Edition (OSE), as
@@ -674,4 +674,5 @@
 	generic/critsectrw-generic.cpp \
 	generic/env-generic.cpp \
+	generic/ftp-server.cpp \
 	generic/RTDirCreateUniqueNumbered-generic.cpp \
 	generic/RTEnvDupEx-generic.cpp \
Index: /trunk/src/VBox/Runtime/generic/ftp-server.cpp
===================================================================
--- /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82665)
+++ /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82665)
@@ -0,0 +1,218 @@
+/* $Id$ */
+/** @file
+ * Generic FTP server (RFC 959) implementation.
+ */
+
+/*
+ * Copyright (C) 2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+/**
+ * Known limitations so far:
+ * - UTF-8 support only.
+ * - No support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++).
+ * - No FTPS / SFTP support.
+ * - No passive mode ("PASV") support.
+ * - No proxy support.
+ * - No FXP support.
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FTP
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/ftp.h>
+#include <iprt/mem.h>
+#include <iprt/poll.h>
+#include <iprt/socket.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+*   Structures and Typedefs                                                                                                      *
+*********************************************************************************************************************************/
+/**
+ * Internal FTP server instance.
+ */
+typedef struct RTFTPSERVERINTERNAL
+{
+    /** Magic value. */
+    uint32_t            u32Magic;
+    /** Pointer to TCP server instance. */
+    PRTTCPSERVER        pTCPServer;
+} RTFTPSERVERINTERNAL;
+/** Pointer to an internal FTP server instance. */
+typedef RTFTPSERVERINTERNAL *PRTFTPSERVERINTERNAL;
+
+
+/*********************************************************************************************************************************
+*   Defined Constants And Macros                                                                                                 *
+*********************************************************************************************************************************/
+/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
+#define RTFTPSERVER_VALID_RETURN_RC(hFTPServer, a_rc) \
+    do { \
+        AssertPtrReturn((hFTPServer), (a_rc)); \
+        AssertReturn((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC, (a_rc)); \
+    } while (0)
+
+/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
+#define RTFTPSERVER_VALID_RETURN(hFTPServer) RTFTPSERVER_VALID_RETURN_RC((hFTPServer), VERR_INVALID_HANDLE)
+
+/** Validates a handle and returns (void) if not valid. */
+#define RTFTPSERVER_VALID_RETURN_VOID(hFTPServer) \
+    do { \
+        AssertPtrReturnVoid(hFTPServer); \
+        AssertReturnVoid((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC); \
+    } while (0)
+
+/** Supported FTP server command IDs.
+ *  Alphabetically, named after their official command names. */
+typedef enum RTFTPSERVER_CMD
+{
+    /** Invalid command, do not use. Always must come first. */
+    RTFTPSERVER_CMD_INVALID = 0,
+    /** Aborts the current command on the server. */
+    RTFTPSERVER_CMD_ABOR,
+    /** Changes the current working directory. */
+    RTFTPSERVER_CMD_CDUP,
+    /** Changes the current working directory. */
+    RTFTPSERVER_CMD_CWD,
+    /** Lists a directory. */
+    RTFTPSERVER_CMD_LS,
+    /** Sends a nop ("no operation") to the server. */
+    RTFTPSERVER_CMD_NOOP,
+    /** Sets the password for authentication. */
+    RTFTPSERVER_CMD_PASS,
+    /** Gets the current working directory. */
+    RTFTPSERVER_CMD_PWD,
+    /** Terminates the session (connection). */
+    RTFTPSERVER_CMD_QUIT,
+    /** Retrieves a specific file. */
+    RTFTPSERVER_CMD_RETR,
+    /** Recursively gets a directory (and its contents). */
+    RTFTPSERVER_CMD_RGET,
+    /** Retrieves the current status of a transfer. */
+    RTFTPSERVER_CMD_STAT,
+    /** Sets the (data) representation type. */
+    RTFTPSERVER_CMD_TYPE,
+    /** Sets the user name for authentication. */
+    RTFTPSERVER_CMD_USER,
+    /** The usual 32-bit hack. */
+    RTFTPSERVER_CMD_32BIT_HACK = 0x7fffffff
+} RTFTPSERVER_CMD;
+
+/**
+ * Structure for maintaining an internal FTP server client state.
+ */
+typedef struct RTFTPSERVERCLIENTSTATE
+{
+    /** Pointer to internal server state. */
+    PRTFTPSERVERINTERNAL pServer;
+    /** Socket handle the client is bound to. */
+    RTSOCKET             hSocket;
+} RTFTPSERVERCLIENTSTATE;
+/** Pointer to an internal FTP server client state. */
+typedef RTFTPSERVERCLIENTSTATE *PRTFTPSERVERCLIENTSTATE;
+
+
+static int rtFTPServerSendReply(PRTFTPSERVERCLIENTSTATE pClient, RTFTPSERVER_REPLY enmReply)
+{
+    RT_NOREF(enmReply);
+
+    RTTcpWrite(pClient->hSocket, "hello\n", sizeof("hello\n") - 1);
+
+    int rc =  0;
+    RT_NOREF(rc);
+
+    return rc;
+}
+
+static int rtFTPServerDoLogin(PRTFTPSERVERCLIENTSTATE pClient)
+{
+    int rc = rtFTPServerSendReply(pClient, RTFTPSERVER_REPLY_READY_FOR_NEW_USER);
+
+    return rc;
+}
+
+static DECLCALLBACK(int) rtFTPServerThread(RTSOCKET hSocket, void *pvUser)
+{
+    PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
+    RTFTPSERVER_VALID_RETURN(pThis);
+
+    RTFTPSERVERCLIENTSTATE ClientState;
+    RT_ZERO(ClientState);
+
+    ClientState.pServer = pThis;
+    ClientState.hSocket = hSocket;
+
+    int rc = rtFTPServerDoLogin(&ClientState);
+
+    return rc;
+}
+
+RTR3DECL(int) RTFTPServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
+                                const char *pcszPathRoot)
+{
+    AssertPtrReturn(phFTPServer,  VERR_INVALID_POINTER);
+    AssertPtrReturn(pcszAddress,  VERR_INVALID_POINTER);
+    AssertPtrReturn(pcszPathRoot, VERR_INVALID_POINTER);
+    AssertReturn   (uPort,        VERR_INVALID_PARAMETER);
+
+    int rc;
+
+    PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
+    if (pThis)
+    {
+        rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
+                               rtFTPServerThread, pThis /* pvUser */, &pThis->pTCPServer);
+    }
+    else
+        rc = VERR_NO_MEMORY;
+
+    return rc;
+}
+
+RTR3DECL(int) RTFTPServerDestroy(RTFTPSERVER hFTPServer)
+{
+    if (hFTPServer == NIL_RTFTPSERVER)
+        return VINF_SUCCESS;
+
+    PRTFTPSERVERINTERNAL pThis = hFTPServer;
+    RTFTPSERVER_VALID_RETURN(pThis);
+
+    AssertPtr(pThis->pTCPServer);
+
+    int rc = RTTcpServerDestroy(pThis->pTCPServer);
+    if (RT_SUCCESS(rc))
+    {
+        pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
+
+        RTMemFree(pThis);
+    }
+
+    return rc;
+}
+
Index: /trunk/src/VBox/Runtime/include/internal/magics.h
===================================================================
--- /trunk/src/VBox/Runtime/include/internal/magics.h	(revision 82664)
+++ /trunk/src/VBox/Runtime/include/internal/magics.h	(revision 82665)
@@ -5,5 +5,5 @@
 
 /*
- * Copyright (C) 2007-2019 Oracle Corporation
+ * Copyright (C) 2007-2020 Oracle Corporation
  *
  * This file is part of VirtualBox Open Source Edition (OSE), as
@@ -84,4 +84,8 @@
 /** The value of RTFILEAIOREQINT::u32Magic. (Stephen Edwin King)  */
 #define RTFILEAIOREQ_MAGIC              UINT32_C(0x19470921)
+/** The magic value for RTFTPSERVERINTERNAL::u32Magic. */
+#define RTFTPSERVER_MAGIC               UINT32_C(0xfeed0004) /**< @todo find a value */
+/** The value of RTFTPSERVERINTERNAL::u32Magic after close. */
+#define RTFTPSERVER_MAGIC_DEAD          (~RTFTPSERVER_MAGIC)
 /** The value of RTENVINTERNAL::u32Magic. (Rumiko Takahashi) */
 #define RTENV_MAGIC                     UINT32_C(0x19571010)
Index: /trunk/src/VBox/Runtime/tools/Makefile.kmk
===================================================================
--- /trunk/src/VBox/Runtime/tools/Makefile.kmk	(revision 82664)
+++ /trunk/src/VBox/Runtime/tools/Makefile.kmk	(revision 82665)
@@ -5,5 +5,5 @@
 
 #
-# Copyright (C) 2006-2019 Oracle Corporation
+# Copyright (C) 2006-2020 Oracle Corporation
 #
 # This file is part of VirtualBox Open Source Edition (OSE), as
@@ -131,4 +131,9 @@
  RTLdrFlt_SOURCES = RTLdrFlt.cpp
 
+ # RTFTPServer implements a simple FTP server.
+ PROGRAMS += RTFTPServer
+ RTFTPServer_TEMPLATE = VBoxR3Tool
+ RTFTPServer_SOURCES = RTFTPServer.cpp
+
  # RTGzip - our gzip clone (for testing the gzip/gunzip streaming code)
  PROGRAMS += RTGzip
Index: /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp
===================================================================
--- /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82665)
+++ /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82665)
@@ -0,0 +1,268 @@
+/* $Id$ */
+/** @file
+ * IPRT - Utility for running a (simple) FTP server.
+ */
+
+/*
+ * Copyright (C) 2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#include <signal.h>
+
+#include <iprt/ftp.h>
+
+#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+*   Global Variables                                                                                                             *
+*********************************************************************************************************************************/
+/** Set by the signal handler when the FTP server shall be terminated. */
+static volatile bool g_fCanceled = false;
+
+
+#ifdef RT_OS_WINDOWS
+static BOOL WINAPI signalHandler(DWORD dwCtrlType)
+{
+    bool fEventHandled = FALSE;
+    switch (dwCtrlType)
+    {
+        /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+         * via GenerateConsoleCtrlEvent(). */
+        case CTRL_BREAK_EVENT:
+        case CTRL_CLOSE_EVENT:
+        case CTRL_C_EVENT:
+            ASMAtomicWriteBool(&g_fCanceled, true);
+            fEventHandled = TRUE;
+            break;
+        default:
+            break;
+        /** @todo Add other events here. */
+    }
+
+    return fEventHandled;
+}
+#else /* !RT_OS_WINDOWS */
+/**
+ * Signal handler that sets g_fCanceled.
+ *
+ * This can be executed on any thread in the process, on Windows it may even be
+ * a thread dedicated to delivering this signal.  Don't do anything
+ * unnecessary here.
+ */
+static void signalHandler(int iSignal)
+{
+    NOREF(iSignal);
+    ASMAtomicWriteBool(&g_fCanceled, true);
+}
+#endif
+
+/**
+ * Installs a custom signal handler to get notified
+ * whenever the user wants to intercept the program.
+ *
+ * @todo Make this handler available for all VBoxManage modules?
+ */
+static int signalHandlerInstall(void)
+{
+    g_fCanceled = false;
+
+    int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
+    {
+        rc = RTErrConvertFromWin32(GetLastError());
+        RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
+    }
+#else
+    signal(SIGINT,   signalHandler);
+    signal(SIGTERM,  signalHandler);
+# ifdef SIGBREAK
+    signal(SIGBREAK, signalHandler);
+# endif
+#endif
+    return rc;
+}
+
+/**
+ * Uninstalls a previously installed signal handler.
+ */
+static int signalHandlerUninstall(void)
+{
+    int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
+    {
+        rc = RTErrConvertFromWin32(GetLastError());
+        RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
+    }
+#else
+    signal(SIGINT,   SIG_DFL);
+    signal(SIGTERM,  SIG_DFL);
+# ifdef SIGBREAK
+    signal(SIGBREAK, SIG_DFL);
+# endif
+#endif
+    return rc;
+}
+
+int main(int argc, char **argv)
+{
+    int rc = RTR3InitExe(argc, &argv, 0);
+    if (RT_FAILURE(rc))
+        return RTMsgInitFailure(rc);
+
+    /* Use some sane defaults. */
+    char     szAddress[64]         = "localhost";
+    uint16_t uPort                 = 2121;
+
+    /* By default use the current directory as serving root directory. */
+    char     szRootDir[RTPATH_MAX];
+    rc = RTPathGetCurrent(szRootDir, sizeof(szRootDir));
+    if (RT_FAILURE(rc))
+        return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
+
+    /*
+     * Parse arguments.
+     */
+    static const RTGETOPTDEF s_aOptions[] =
+    {
+        { "--address",      'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
+        /** @todo Implement IPv6 support? */
+        { "--port",         'p', RTGETOPT_REQ_UINT16 },
+        { "--root-dir",     'r', RTGETOPT_REQ_STRING },
+        { "--verbose",      'v', RTGETOPT_REQ_NOTHING }
+    };
+
+    RTEXITCODE      rcExit          = RTEXITCODE_SUCCESS;
+    unsigned        uVerbosityLevel = 1;
+
+    RTGETOPTUNION   ValueUnion;
+    RTGETOPTSTATE   GetState;
+    RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+    while ((rc = RTGetOpt(&GetState, &ValueUnion)))
+    {
+        switch (rc)
+        {
+            case 'a':
+                RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
+                             ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
+                break;
+
+            case 'p':
+                uPort = ValueUnion.u16;
+                break;
+
+            case 'v':
+                uVerbosityLevel++;
+                break;
+
+            case 'h':
+                RTPrintf("Usage: %s [options]\n"
+                         "\n"
+                         "Options:\n"
+                         "  -a, --address (default: localhost)\n"
+                         "      Specifies the address to use for listening.\n"
+                         "  -p, --port (default: 2121)\n"
+                         "      Specifies the port to use for listening.\n"
+                         "  -r, --root-dir (default: current dir)\n"
+                         "      Specifies the root directory being served.\n"
+                         "  -v, --verbose\n"
+                         "      Controls the verbosity level.\n"
+                         "  -h, -?, --help\n"
+                         "      Display this help text and exit successfully.\n"
+                         "  -V, --version\n"
+                         "      Display the revision and exit successfully.\n"
+                         , RTPathFilename(argv[0]));
+                return RTEXITCODE_SUCCESS;
+
+            case 'V':
+                RTPrintf("$Revision$\n");
+                return RTEXITCODE_SUCCESS;
+
+            default:
+                return RTGetOptPrintError(rc, &ValueUnion);
+        }
+    }
+
+    /* Install signal handler. */
+    rc = signalHandlerInstall();
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Create the FTP server instance.
+         */
+        RTFTPSERVER hFTPServer;
+        rc = RTFTPServerCreate(&hFTPServer, szAddress, uPort, szRootDir);
+        if (RT_SUCCESS(rc))
+        {
+            RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
+            RTPrintf("Root directory is '%s'\n", szRootDir);
+
+            RTPrintf("Running FTP server ...\n");
+
+            for (;;)
+            {
+                RTThreadSleep(200);
+
+                if (g_fCanceled)
+                    break;
+            }
+
+            RTPrintf("Stopping FTP server ...\n");
+
+            int rc2 = RTFTPServerDestroy(hFTPServer);
+            if (RT_SUCCESS(rc))
+                rc = rc2;
+
+            RTPrintf("Stopped FTP server\n");
+        }
+        else
+            rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFTPServerCreate failed: %Rrc", rc);
+
+        int rc2 = signalHandlerUninstall();
+        if (RT_SUCCESS(rc))
+            rc = rc2;
+    }
+
+    /* Set rcExit on failure in case we forgot to do so before. */
+    if (RT_FAILURE(rc))
+        rcExit = RTEXITCODE_FAILURE;
+
+    return rcExit;
+}
+
