Index: /trunk/include/iprt/ftp.h
===================================================================
--- /trunk/include/iprt/ftp.h	(revision 82731)
+++ /trunk/include/iprt/ftp.h	(revision 82732)
@@ -76,6 +76,6 @@
 typedef enum RTFTPSERVER_TRANSFER_MODE
 {
-    RTFTPSERVER_TRANSFER_MODE_UNKNOWN = 0,
-    RTFTPSERVER_TRANSFER_MODE_STREAM,
+    /** Default if nothing else is set. */
+    RTFTPSERVER_TRANSFER_MODE_STREAM = 0,
     RTFTPSERVER_TRANSFER_MODE_BLOCK,
     RTFTPSERVER_TRANSFER_MODE_COMPRESSED,
@@ -89,6 +89,6 @@
 typedef enum RTFTPSERVER_DATA_TYPE
 {
-    RTFTPSERVER_DATA_TYPE_UNKNOWN = 0,
-    RTFTPSERVER_DATA_TYPE_ASCII,
+    /** Default if nothing else is set. */
+    RTFTPSERVER_DATA_TYPE_ASCII = 0,
     RTFTPSERVER_DATA_TYPE_EBCDIC,
     RTFTPSERVER_DATA_TYPE_IMAGE,
@@ -99,4 +99,17 @@
 
 /**
+ * Enumeration for defining the struct type.
+ */
+typedef enum RTFTPSERVER_STRUCT_TYPE
+{
+    /** Default if nothing else is set. */
+    RTFTPSERVER_STRUCT_TYPE_FILE = 0,
+    RTFTPSERVER_STRUCT_TYPE_RECORD,
+    RTFTPSERVER_STRUCT_TYPE_PAGE,
+    /** The usual 32-bit hack. */
+    RTFTPSERVER_STRUCT_TYPE_32BIT_HACK = 0x7fffffff
+} RTFTPSERVER_STRUCT_TYPE;
+
+/**
  * Enumeration for FTP server reply codes.
  *
@@ -107,4 +120,6 @@
     /** Invalid reply type, do not use. */
     RTFTPSERVER_REPLY_INVALID                        = 0,
+    /** Command okay. */
+    RTFTPSERVER_REPLY_FILE_STATUS_OKAY               = 150,
     /** Command okay. */
     RTFTPSERVER_REPLY_OKAY                           = 200,
@@ -156,4 +171,6 @@
     /** Current set data type. */
     RTFTPSERVER_DATA_TYPE       enmDataType;
+    /** Current set struct type. */
+    RTFTPSERVER_STRUCT_TYPE     enmStructType;
 } RTFTPSERVERCLIENTSTATE;
 /** Pointer to a FTP server client state. */
@@ -182,8 +199,4 @@
 typedef struct RTFTPSERVERCALLBACKS
 {
-    /** User pointer to data. Optional and can be NULL. */
-    void  *pvUser;
-    /** Size (in bytes) of user data pointing at. Optional and can be 0. */
-    size_t cbUser;
     /**
      * Callback which gets invoked when a user connected.
@@ -208,8 +221,35 @@
      * @returns VBox status code.
      * @param   pData           Pointer to generic callback data.
-     */
-    DECLCALLBACKMEMBER(int,  pfnOnUserDisconnect)(PRTFTPCALLBACKDATA pData);
+     * @param   pcszUser        User name which disconnected.
+     */
+    DECLCALLBACKMEMBER(int,  pfnOnUserDisconnect)(PRTFTPCALLBACKDATA pData, const char *pcszUser);
+    /**
+     * Callback which gets invoked when the client wants to start reading or writing a file.
+     *
+     * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
+     * @param   pcsszPath       Path of file to handle.
+     * @param   fMode           File mode to use (IPRT stlye).
+     * @param   ppvHandle       Opaque file handle only known to the callback implementation.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnFileOpen)(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle);
+    /**
+     * Callback which gets invoked when the client wants to read from a file.
+     *
+     * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
+     * @param   pvHandle        Opaque file handle only known to the callback implementation.
+     * @param   pvBuf           Where to store the read file data.
+     * @param   cbToRead        How much (in bytes) to read. Must at least supply the size of pvBuf.
+     * @param   pcbRead         How much (in bytes) was read. Optional.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnFileRead)(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead);
+    /**
+     * Callback which gets invoked when the client is done reading from or writing to a file.
+     *
+     * @returns VBox status code.
+     * @param   pData           Pointer to generic callback data.
+     * @param   ppvHandle       Opaque file handle only known to the callback implementation.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnFileClose)(PRTFTPCALLBACKDATA pData, void *pvHandle);
     /**
@@ -278,7 +318,9 @@
  * @param   uPort               The port for creating a listening socket.
  * @param   pCallbacks          Callback table to use.
+ * @param   pvUser              Pointer to user-specific data. Optional.
+ * @param   cbUser              Size of user-specific data. Optional.
  */
 RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
-                                PRTFTPSERVERCALLBACKS pCallbacks);
+                                PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser);
 
 /**
Index: /trunk/src/VBox/Runtime/generic/ftp-server.cpp
===================================================================
--- /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82731)
+++ /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82732)
@@ -29,4 +29,5 @@
  * Known limitations so far:
  * - UTF-8 support only.
+ * - Only supports ASCII + binary (image type) file streams for now.
  * - No support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++).
  * - No FTPS / SFTP support.
@@ -48,5 +49,5 @@
 #include <iprt/asm.h>
 #include <iprt/assert.h>
-#include <iprt/errcore.h>
+#include <iprt/err.h>
 #include <iprt/file.h> /* For file mode flags. */
 #include <iprt/getopt.h>
@@ -77,4 +78,8 @@
     /** Number of currently connected clients. */
     uint32_t                cClients;
+    /** Pointer to user-specific data. Optional. */
+    void                   *pvUser;
+    /** Size of user-specific data. Optional. */
+    size_t                  cbUser;
 } RTFTPSERVERINTERNAL;
 /** Pointer to an internal FTP server instance. */
@@ -134,4 +139,6 @@
     /** Retrieves the current status of a transfer. */
     RTFTPSERVER_CMD_STAT,
+    /** Sets the structure type to use. */
+    RTFTPSERVER_CMD_STRU,
     /** Gets the server's OS info. */
     RTFTPSERVER_CMD_SYST,
@@ -164,4 +171,9 @@
     /** Thread stop indicator. */
     volatile bool               fStop;
+    /** Thread stopped indicator. */
+    volatile bool               fStopped;
+    /** Overall result of data connection on stop. */
+    int                         rc;
+    /** For now we only support sending a single file per active data connection. */
     char                        szFile[RTPATH_MAX];
 } RTFTPSERVERDATACONN;
@@ -199,5 +211,5 @@
         if (pCallbacks->a_Name) \
         { \
-            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            RTFTPCALLBACKDATA Data = { &pClient->State }; \
             return pCallbacks->a_Name(&Data); \
         } \
@@ -213,5 +225,5 @@
         if (pCallbacks->a_Name) \
         { \
-            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
             rc = pCallbacks->a_Name(&Data); \
         } \
@@ -227,5 +239,5 @@
         if (pCallbacks->a_Name) \
         { \
-            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
             rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
         } \
@@ -241,5 +253,5 @@
         if (pCallbacks->a_Name) \
         { \
-            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
             return pCallbacks->a_Name(&Data, __VA_ARGS__); \
         } \
@@ -253,5 +265,7 @@
 *********************************************************************************************************************************/
 
-static int rtFtpServerDataPortOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort);
+static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn);
+static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort);
+static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState);
 
 /**
@@ -271,4 +285,5 @@
 static FNRTFTPSERVERCMD rtFtpServerHandleSIZE;
 static FNRTFTPSERVERCMD rtFtpServerHandleSTAT;
+static FNRTFTPSERVERCMD rtFtpServerHandleSTRU;
 static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
 static FNRTFTPSERVERCMD rtFtpServerHandleTYPE;
@@ -307,4 +322,5 @@
     { RTFTPSERVER_CMD_SIZE,     "SIZE",         rtFtpServerHandleSIZE },
     { RTFTPSERVER_CMD_STAT,     "STAT",         rtFtpServerHandleSTAT },
+    { RTFTPSERVER_CMD_STRU,     "STRU",         rtFtpServerHandleSTRU },
     { RTFTPSERVER_CMD_SYST,     "SYST",         rtFtpServerHandleSYST },
     { RTFTPSERVER_CMD_TYPE,     "TYPE",         rtFtpServerHandleTYPE },
@@ -543,8 +559,9 @@
  * @param   uPort               Port for the data connection.
  */
-static int rtFtpServerDataPortOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort)
-{
-    RT_NOREF(pAddr);
-
+static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort)
+{
+    LogFlowFuncEnter();
+
+    /** @todo Implement IPv6 handling here. */
     char szAddress[32];
     const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8",
@@ -561,5 +578,5 @@
  * @param   pDataConn           Data connection to close.
  */
-static int rtFtpServerDataPortClose(PRTFTPSERVERDATACONN pDataConn)
+static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn)
 {
     int rc = VINF_SUCCESS;
@@ -567,9 +584,31 @@
     if (pDataConn->hSocket != NIL_RTSOCKET)
     {
-        rc = RTTcpClientClose(pDataConn->hSocket);
-        pDataConn->hSocket = NIL_RTSOCKET;
-    }
-
-    return rc;
+        LogFlowFuncEnter();
+
+        rc = RTTcpFlush(pDataConn->hSocket);
+        if (RT_SUCCESS(rc))
+        {
+            rc = RTTcpClientClose(pDataConn->hSocket);
+            pDataConn->hSocket = NIL_RTSOCKET;
+        }
+    }
+
+    return rc;
+}
+
+/**
+ * Writes data to the data connection.
+ *
+ * @returns VBox status code.
+ * @param   pDataConn           Data connection to write to.
+ * @param   pvData              Data to write.
+ * @param   cbData              Size (in bytes) of data to write.
+ * @param   pcbWritten          How many bytes were written. Optional and unused atm.
+ */
+static int rtFtpServerDataConnWrite(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData, size_t *pcbWritten)
+{
+    RT_NOREF(pcbWritten);
+
+    return RTTcpWrite(pDataConn->hSocket, pvData, cbData);
 }
 
@@ -590,5 +629,7 @@
     PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn;
 
-    int rc = rtFtpServerDataPortOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort);
+    LogFlowFuncEnter();
+
+    int rc = rtFtpServerDataConnOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort);
     if (RT_FAILURE(rc))
         return rc;
@@ -599,5 +640,5 @@
         return VERR_NO_MEMORY;
 
-    pDataConn->fStop    = false;
+    /* Set start indicator. */
     pDataConn->fStarted = true;
 
@@ -612,25 +653,38 @@
     if (RT_SUCCESS(rc))
     {
+        LogFlowFunc(("Transfer started\n"));
+
         do
         {
             size_t cbRead = 0;
             RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead);
-            if (RT_SUCCESS(rc))
-                rc = RTTcpWrite(pClient->DataConn.hSocket, pvBuf, cbRead);
+            if (   RT_SUCCESS(rc)
+                && cbRead)
+            {
+                rc = rtFtpServerDataConnWrite(pDataConn, pvBuf, cbRead, NULL /* pcbWritten */);
+            }
 
             if (   !cbRead
                 || ASMAtomicReadBool(&pDataConn->fStop))
+            {
                 break;
+            }
         }
         while (RT_SUCCESS(rc));
 
         RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle);
-    }
-
-    rtFtpServerDataPortClose(&pClient->DataConn);
+
+        LogFlowFunc(("Transfer done\n"));
+    }
+
+    rtFtpServerDataConnClose(pDataConn);
 
     RTMemFree(pvBuf);
     pvBuf = NULL;
 
+    pDataConn->fStopped = true;
+    pDataConn->rc       = rc;
+
+    LogFlowFuncLeaveRC(rc);
     return rc;
 }
@@ -683,12 +737,6 @@
     if (RT_SUCCESS(rc))
     {
-        if (pDataConn->hSocket != NIL_RTSOCKET)
-        {
-            RTTcpClientClose(pDataConn->hSocket);
-            pDataConn->hSocket = NIL_RTSOCKET;
-        }
-
-        pDataConn->fStarted = false;
-        pDataConn->hThread  = NIL_RTTHREAD;
+        rtFtpServerDataConnClose(pDataConn);
+        rtFtpServerDataConnReset(pDataConn);
 
         rc = rcThread;
@@ -696,4 +744,23 @@
 
     return rc;
+}
+
+/**
+ * Resets a data connection structure.
+ *
+ * @returns VBox status code.
+ * @param   pDataConn           Data connection structure to reset.
+ */
+static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn)
+{
+    LogFlowFuncEnter();
+
+    pDataConn->hSocket  = NIL_RTSOCKET;
+    pDataConn->uPort    = 20; /* Default port to use. */
+    pDataConn->hThread  = NIL_RTTHREAD;
+    pDataConn->fStarted = false;
+    pDataConn->fStop    = false;
+    pDataConn->fStopped = false;
+    pDataConn->rc       = VERR_IPE_UNINITIALIZED_STATUS;
 }
 
@@ -814,5 +881,5 @@
 
     /* Only allow one data connection per client at a time. */
-    rtFtpServerDataPortClose(&pClient->DataConn);
+    rtFtpServerDataConnClose(&pClient->DataConn);
 
     int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConn.Addr, &pClient->DataConn.uPort);
@@ -863,11 +930,11 @@
     if (RT_SUCCESS(rc))
     {
-        rc = RTStrCopy(pClient->DataConn.szFile, sizeof(pClient->DataConn.szFile), pcszPath);
+        rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_FILE_STATUS_OKAY);
         if (RT_SUCCESS(rc))
         {
-            rc = rtFtpServerDataConnCreate(pClient, &pClient->DataConn);
+            rc = RTStrCopy(pClient->DataConn.szFile, sizeof(pClient->DataConn.szFile), pcszPath);
             if (RT_SUCCESS(rc))
             {
-                rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
+                rc = rtFtpServerDataConnCreate(pClient, &pClient->DataConn);
             }
         }
@@ -950,4 +1017,25 @@
 }
 
+static int rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+{
+    if (cArgs != 1)
+        return VERR_INVALID_PARAMETER;
+
+    const char *pcszType = apcszArgs[0];
+
+    int rc;
+
+    if (!RTStrICmp(pcszType, "F"))
+    {
+        pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
+
+        rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
+    }
+    else
+        rc = VERR_NOT_IMPLEMENTED;
+
+    return rc;
+}
+
 static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
 {
@@ -971,5 +1059,5 @@
     int rc = VINF_SUCCESS;
 
-    if (!RTStrICmp(pcszType, "A")) /* ASCII (can be 7 or 8 bits). */
+    if (!RTStrICmp(pcszType, "A"))
     {
         pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
@@ -980,5 +1068,8 @@
     }
     else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
-        rc = VERR_INVALID_PARAMETER;
+        rc = VERR_NOT_IMPLEMENTED;
+
+    if (RT_SUCCESS(rc))
+        rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
 
     return rc;
@@ -993,9 +1084,5 @@
     AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
 
-    if (pClient->State.pszUser)
-    {
-        RTStrFree(pClient->State.pszUser);
-        pClient->State.pszUser = NULL;
-    }
+    rtFtpServerClientStateReset(&pClient->State);
 
     int rc = rtFtpServerLookupUser(pClient, pcszUser);
@@ -1069,8 +1156,101 @@
 
 /**
- * Main loop for processing client commands.
+ * Main function for processing client commands for the control connection.
  *
  * @returns VBox status code.
  * @param   pClient             Client to process commands for.
+ * @param   pcszCmd             Command string to parse and handle.
+ * @param   cbCmd               Size (in bytes) of command string.
+ */
+static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pcszCmd, size_t cbCmd)
+{
+    /* Make sure to terminate the string in any case. */
+    pcszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0';
+
+    /* A tiny bit of sanitation. */
+    RTStrStripL(pcszCmd);
+
+    /* First, terminate string by finding the command end marker (telnet style). */
+    /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
+    char *pszCmdEnd = RTStrIStr(pcszCmd, "\r\n");
+    if (pszCmdEnd)
+        *pszCmdEnd = '\0';
+
+    int rcCmd = VINF_SUCCESS;
+
+    uint8_t cArgs     = 0;
+    char  **papszArgs = NULL;
+    int rc = rtFtpServerCmdArgsParse(pcszCmd, &cArgs, &papszArgs);
+    if (   RT_SUCCESS(rc)
+        && cArgs) /* At least the actual command (without args) must be present. */
+    {
+        unsigned i = 0;
+        for (; i < RT_ELEMENTS(g_aCmdMap); i++)
+        {
+            if (!RTStrICmp(papszArgs[0], g_aCmdMap[i].szCmd))
+            {
+                /* Save timestamp of last command sent. */
+                pClient->State.tsLastCmdMs = RTTimeMilliTS();
+
+                rcCmd = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
+                break;
+            }
+        }
+
+        rtFtpServerCmdArgsFree(papszArgs);
+
+        if (i == RT_ELEMENTS(g_aCmdMap))
+        {
+            int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
+            if (RT_SUCCESS(rc))
+                rc = rc2;
+
+            return rc;
+        }
+
+        const bool fDisconnect =    g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
+                                 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
+        if (fDisconnect)
+        {
+            int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CLOSING_CTRL_CONN);
+            if (RT_SUCCESS(rc))
+                rc = rc2;
+
+            RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser);
+            return rc;
+        }
+
+        switch (rcCmd)
+        {
+            case VERR_INVALID_PARAMETER:
+                RT_FALL_THROUGH();
+            case VERR_INVALID_POINTER:
+                rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
+                break;
+
+            case VERR_NOT_IMPLEMENTED:
+                rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
+                break;
+
+            default:
+                break;
+        }
+    }
+    else
+    {
+        int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
+        if (RT_SUCCESS(rc))
+            rc = rc2;
+    }
+
+    LogFlowFuncLeaveRC(rc);
+    return rc;
+}
+
+/**
+ * Main loop for processing client commands.
+ *
+ * @returns VBox status code.
+ * @param   pClient             Client to process commands for.
  */
 static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
@@ -1078,96 +1258,44 @@
     int rc;
 
+    size_t cbRead;
+    char   szCmd[RTFTPSERVER_MAX_CMD_LEN + 1];
+
     for (;;)
     {
-        size_t cbRead;
-        char   szCmd[RTFTPSERVER_MAX_CMD_LEN];
-        rc = RTTcpRead(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
+        rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */
         if (RT_SUCCESS(rc))
         {
-            /* Make sure to terminate the string in any case. */
-            szCmd[RTFTPSERVER_MAX_CMD_LEN - 1] = '\0';
-
-            /* A tiny bit of sanitation. */
-            RTStrStripL(szCmd);
-
-            /* First, terminate string by finding the command end marker (telnet style). */
-            /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
-            char *pszCmdEnd = RTStrIStr(szCmd, "\r\n");
-            if (pszCmdEnd)
-                *pszCmdEnd = '\0';
-
-            int rcCmd = VINF_SUCCESS;
-
-            uint8_t cArgs     = 0;
-            char  **papszArgs = NULL;
-            rc = rtFtpServerCmdArgsParse(szCmd, &cArgs, &papszArgs);
+            rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
             if (   RT_SUCCESS(rc)
-                && cArgs) /* At least the actual command (without args) must be present. */
+                && cbRead)
             {
-                unsigned i = 0;
-                for (; i < RT_ELEMENTS(g_aCmdMap); i++)
-                {
-                    if (!RTStrICmp(papszArgs[0], g_aCmdMap[i].szCmd))
-                    {
-                        /* Save timestamp of last command sent. */
-                        pClient->State.tsLastCmdMs = RTTimeMilliTS();
-
-                        rcCmd = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
-                        break;
-                    }
-                }
-
-                rtFtpServerCmdArgsFree(papszArgs);
-
-                if (i == RT_ELEMENTS(g_aCmdMap))
-                {
-                    int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
-                    if (RT_SUCCESS(rc))
-                        rc = rc2;
-
-                    continue;
-                }
-
-                const bool fDisconnect =    g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
-                                         || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
-                if (fDisconnect)
-                {
-                    int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CLOSING_CTRL_CONN);
-                    if (RT_SUCCESS(rc))
-                        rc = rc2;
-
-                    RTFTPSERVER_HANDLE_CALLBACK(pfnOnUserDisconnect);
-                    break;
-                }
-
-                switch (rcCmd)
-                {
-                    case VERR_INVALID_PARAMETER:
-                        RT_FALL_THROUGH();
-                    case VERR_INVALID_POINTER:
-                        rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
-                        break;
-
-                    case VERR_NOT_IMPLEMENTED:
-                        rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
-                        break;
-
-                    default:
-                        break;
-                }
+                AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW);
+                rc = rtFtpServerProcessCommands(pClient, szCmd, cbRead);
             }
-            else
+        }
+        else
+        {
+            if (rc == VERR_TIMEOUT)
+                rc = VINF_SUCCESS;
+
+            if (RT_FAILURE(rc))
+                break;
+
+            PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn;
+
+            if (   ASMAtomicReadBool(&pDataConn->fStarted)
+                && ASMAtomicReadBool(&pDataConn->fStopped))
             {
-                int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
+                Assert(pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS);
+
+                rc = rtFtpServerSendReplyRc(pClient, RT_SUCCESS(pDataConn->rc)
+                                                     ? RTFTPSERVER_REPLY_CLOSING_DATA_CONN
+                                                     : RTFTPSERVER_REPLY_CONN_CLOSED_TRANSFER_ABORTED);
+
+                int rc2 = rtFtpServerDataConnDestroy(pClient, pDataConn);
                 if (RT_SUCCESS(rc))
                     rc = rc2;
             }
         }
-        else
-        {
-            int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_RECOGNIZED);
-            if (RT_SUCCESS(rc))
-                rc = rc2;
-        }
     }
 
@@ -1177,4 +1305,5 @@
         rc = rc2;
 
+    LogFlowFuncLeaveRC(rc);
     return rc;
 }
@@ -1187,8 +1316,13 @@
 static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
 {
+    LogFlowFuncEnter();
+
     RTStrFree(pState->pszUser);
     pState->pszUser = NULL;
 
-    pState->tsLastCmdMs = RTTimeMilliTS();
+    pState->cFailedLoginAttempts = 0;
+    pState->tsLastCmdMs          = RTTimeMilliTS();
+    pState->enmDataType          = RTFTPSERVER_DATA_TYPE_ASCII;
+    pState->enmStructType        = RTFTPSERVER_STRUCT_TYPE_FILE;
 }
 
@@ -1230,5 +1364,5 @@
 
 RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
-                                PRTFTPSERVERCALLBACKS pCallbacks)
+                                PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
 {
     AssertPtrReturn(phFTPServer,  VERR_INVALID_POINTER);
@@ -1236,4 +1370,5 @@
     AssertReturn   (uPort,        VERR_INVALID_PARAMETER);
     AssertPtrReturn(pCallbacks,   VERR_INVALID_POINTER);
+    /* pvUser is optional. */
 
     int rc;
@@ -1244,4 +1379,6 @@
         pThis->u32Magic  = RTFTPSERVER_MAGIC;
         pThis->Callbacks = *pCallbacks;
+        pThis->pvUser    = pvUser;
+        pThis->cbUser    = cbUser;
 
         rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
Index: /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp
===================================================================
--- /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82731)
+++ /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82732)
@@ -186,9 +186,9 @@
 }
 
-static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData)
+static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
 {
     RT_NOREF(pData);
 
-    RTPrintf("User disconnected\n");
+    RTPrintf("User '%s' disconnected\n", pcszUser);
 
     return VINF_SUCCESS;
@@ -222,5 +222,11 @@
     Assert(pData->cbUser == sizeof(FTPSERVERDATA));
 
-    return RTFileClose(pThis->hFile);
+    int rc = RTFileClose(pThis->hFile);
+    if (RT_SUCCESS(rc))
+    {
+        pThis->hFile = NIL_RTFILE;
+    }
+
+    return rc;
 }
 
@@ -406,7 +412,4 @@
         RT_ZERO(Callbacks);
 
-        Callbacks.pvUser                = &g_FTPServerData;
-        Callbacks.cbUser                = sizeof(g_FTPServerData);
-
         Callbacks.pfnOnUserConnect      = onUserConnect;
         Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
@@ -423,5 +426,6 @@
 
         RTFTPSERVER hFTPServer;
-        rc = RTFtpServerCreate(&hFTPServer, szAddress, uPort, &Callbacks);
+        rc = RTFtpServerCreate(&hFTPServer, szAddress, uPort, &Callbacks,
+                               &g_FTPServerData, sizeof(g_FTPServerData));
         if (RT_SUCCESS(rc))
         {
