Index: /trunk/include/iprt/ftp.h
===================================================================
--- /trunk/include/iprt/ftp.h	(revision 82701)
+++ /trunk/include/iprt/ftp.h	(revision 82702)
@@ -108,4 +108,6 @@
     /** Service ready for new user. */
     RTFTPSERVER_REPLY_READY_FOR_NEW_USER             = 220,
+    /** Service is closing control connection. */
+    RTFTPSERVER_REPLY_CLOSING_CTRL_CONN              = 221,
     /** Closing data connection. */
     RTFTPSERVER_REPLY_CLOSING_DATA_CONN              = 226,
@@ -137,4 +139,8 @@
 typedef struct RTFTPSERVERCLIENTSTATE
 {
+    /** User name. */
+    char         *pszUser;
+    /** Number of failed login attempts. */
+    uint8_t       cFailedLoginAttempts;
     /** Timestamp (in ms) of last command issued by the client. */
     uint64_t      tsLastCmdMs;
Index: /trunk/src/VBox/Runtime/generic/ftp-server.cpp
===================================================================
--- /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82701)
+++ /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82702)
@@ -195,4 +195,5 @@
 static FNRTFTPSERVERCMD rtFtpServerHandleMODE;
 static FNRTFTPSERVERCMD rtFtpServerHandleNOOP;
+static FNRTFTPSERVERCMD rtFtpServerHandlePASS;
 static FNRTFTPSERVERCMD rtFtpServerHandlePORT;
 static FNRTFTPSERVERCMD rtFtpServerHandlePWD;
@@ -203,4 +204,5 @@
 static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
 static FNRTFTPSERVERCMD rtFtpServerHandleTYPE;
+static FNRTFTPSERVERCMD rtFtpServerHandleUSER;
 
 /**
@@ -228,4 +230,5 @@
     { RTFTPSERVER_CMD_MODE,     "MODE",         rtFtpServerHandleMODE },
     { RTFTPSERVER_CMD_NOOP,     "NOOP",         rtFtpServerHandleNOOP },
+    { RTFTPSERVER_CMD_PASS,     "PASS",         rtFtpServerHandlePASS },
     { RTFTPSERVER_CMD_PORT,     "PORT",         rtFtpServerHandlePORT },
     { RTFTPSERVER_CMD_PWD,      "PWD",          rtFtpServerHandlePWD  },
@@ -236,4 +239,5 @@
     { RTFTPSERVER_CMD_SYST,     "SYST",         rtFtpServerHandleSYST },
     { RTFTPSERVER_CMD_TYPE,     "TYPE",         rtFtpServerHandleTYPE },
+    { RTFTPSERVER_CMD_USER,     "USER",         rtFtpServerHandleUSER },
     { RTFTPSERVER_CMD_LAST,     "",             NULL }
 };
@@ -370,4 +374,29 @@
 }
 
+static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+{
+    if (cArgs != 1)
+        return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
+
+    const char *pcszPassword = apcszArgs[0];
+    AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
+
+    int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
+    if (RT_SUCCESS(rc))
+    {
+        rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
+    }
+    else
+    {
+        pClient->State.cFailedLoginAttempts++;
+
+        int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
+        if (RT_SUCCESS(rc))
+            rc = rc2;
+    }
+
+    return rc;
+}
+
 static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
 {
@@ -451,61 +480,42 @@
 }
 
+static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+{
+    if (cArgs != 1)
+        return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
+
+    const char *pcszUser = apcszArgs[0];
+    AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
+
+    if (pClient->State.pszUser)
+    {
+        RTStrFree(pClient->State.pszUser);
+        pClient->State.pszUser = NULL;
+    }
+
+    int rc = rtFtpServerLookupUser(pClient, pcszUser);
+    if (RT_SUCCESS(rc))
+    {
+        pClient->State.pszUser = RTStrDup(pcszUser);
+        AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
+
+        rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
+    }
+    else
+    {
+        pClient->State.cFailedLoginAttempts++;
+
+        int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
+        if (RT_SUCCESS(rc))
+            rc = rc2;
+    }
+
+    return rc;
+}
+
 
 /*********************************************************************************************************************************
 *   Internal server functions                                                                                                    *
 *********************************************************************************************************************************/
-
-/**
- * Handles the client's login procedure.
- *
- * @returns VBox status code.
- * @param   pClient             Client to handle login procedure for.
- */
-static int rtFtpServerDoLogin(PRTFTPSERVERCLIENT pClient)
-{
-    LogFlowFuncEnter();
-
-    /* Send welcome message. */
-    int rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_READY_FOR_NEW_USER);
-    if (RT_SUCCESS(rc))
-    {
-        size_t cbRead;
-
-        char szUser[64];
-        rc = RTTcpRead(pClient->hSocket, szUser, sizeof(szUser), &cbRead);
-        if (RT_SUCCESS(rc))
-        {
-            rc = rtFtpServerLookupUser(pClient, szUser);
-            if (RT_SUCCESS(rc))
-            {
-                rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
-                if (RT_SUCCESS(rc))
-                {
-                    char szPass[64];
-                    rc = RTTcpRead(pClient->hSocket, szPass, sizeof(szPass), &cbRead);
-                    {
-                        if (RT_SUCCESS(rc))
-                        {
-                            rc = rtFtpServerAuthenticate(pClient, szUser, szPass);
-                            if (RT_SUCCESS(rc))
-                            {
-                                rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    if (RT_FAILURE(rc))
-    {
-        int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
-        if (RT_SUCCESS(rc))
-            rc = rc2;
-    }
-
-    return rc;
-}
 
 /**
@@ -582,23 +592,19 @@
                 *pszCmdEnd = '\0';
 
-            /* Second, determine if there is any parameters following. */
-            char *pszCmdParms = RTStrIStr(szCmd, " ");
-            if (pszCmdParms)
-                pszCmdParms++;
-
             uint8_t cArgs     = 0;
             char  **papszArgs = NULL;
-            rc = rtFtpServerCmdArgsParse(pszCmdParms, &cArgs, &papszArgs);
-            if (RT_SUCCESS(rc))
+            rc = rtFtpServerCmdArgsParse(szCmd, &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(szCmd, g_aCmdMap[i].szCmd))
+                    if (!RTStrICmp(papszArgs[0], g_aCmdMap[i].szCmd))
                     {
                         /* Save timestamp of last command sent. */
                         pClient->State.tsLastCmdMs = RTTimeMilliTS();
 
-                        rc = g_aCmdMap[i].pfnCmd(pClient, cArgs, papszArgs);
+                        rc = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
                         break;
                     }
@@ -614,6 +620,12 @@
                 }
 
-                if (g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT)
+                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_RET(pfnOnUserDisconnect);
                     break;
@@ -645,4 +657,7 @@
 static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
 {
+    RTStrFree(pState->pszUser);
+    pState->pszUser = NULL;
+
     pState->tsLastCmdMs = RTTimeMilliTS();
 }
@@ -668,5 +683,6 @@
     rtFtpServerClientStateReset(&Client.State);
 
-    int rc = rtFtpServerDoLogin(&Client);
+    /* Send welcome message. */
+    int rc = rtFtpServerSendReplyRc(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER);
     if (RT_SUCCESS(rc))
     {
@@ -677,4 +693,6 @@
         ASMAtomicDecU32(&pThis->cClients);
     }
+
+    rtFtpServerClientStateReset(&Client.State);
 
     return rc;
