Index: /trunk/include/iprt/ftp.h
===================================================================
--- /trunk/include/iprt/ftp.h	(revision 82714)
+++ /trunk/include/iprt/ftp.h	(revision 82715)
@@ -175,10 +175,56 @@
     /** Size (in bytes) of user data pointing at. Optional and can be 0. */
     size_t cbUser;
+    /**
+     * Callback which gets invoked when a user connected.
+     *
+     * @returns VBox status code.
+     * @param   pcszUser        User name.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnUserConnect)(PRTFTPCALLBACKDATA pData, const char *pcszUser);
+    /**
+     * Callback which gets invoked when a user tries to authenticate with a password.
+     *
+     * @returns VBox status code.
+     * @param   pcszUser        User name to authenticate.
+     * @param   pcszPassword    Password to authenticate with.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnUserAuthenticate)(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword);
+    /**
+     * Callback which gets invoked when a user disconnected.
+     *
+     * @returns VBox status code.
+     * @param   pcszUser        User name which disconnected.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnUserDisconnect)(PRTFTPCALLBACKDATA pData);
+    /**
+     * Callback which gets invoked when setting the current working directory.
+     *
+     * @returns VBox status code.
+     * @param   pcszCWD         Current working directory to set.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnPathSetCurrent)(PRTFTPCALLBACKDATA pData, const char *pcszCWD);
+    /**
+     * Callback which gets invoked when a client wants to retrieve the current working directory.
+     *
+     * @returns VBox status code.
+     * @param   pszPWD          Where to store the current working directory.
+     * @param   cbPWD           Size of buffer in bytes.
+     */
     DECLCALLBACKMEMBER(int,  pfnOnPathGetCurrent)(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD);
-    DECLCALLBACKMEMBER(int,  pfnOnList)(PRTFTPCALLBACKDATA pData, void **ppvData, size_t *pcbData);
+    /**
+     * Callback which gets invoked when the client wants to move up a directory (relative to the current working directory).
+     *
+     * @returns VBox status code.
+     */
+    DECLCALLBACKMEMBER(int,  pfnOnPathUp)(PRTFTPCALLBACKDATA pData);
+    /**
+     * Callback which gets invoked when the client wants to list a directory or file.
+     *
+     * @param   pcszPath        Path of file / directory to list. Optional. If NULL, the current directory will be listed.
+     * @param   ppvData         Where to return the listing data. Must be free'd by the caller.
+     * @param   pcbvData        Where to return the listing data size in bytes.
+     * @returns VBox status code.
+     */
+    DECLCALLBACKMEMBER(int,  pfnOnList)(PRTFTPCALLBACKDATA pData, const char *pcszPath, void **ppvData, size_t *pcbData);
 } RTFTPSERVERCALLBACKS;
 /** Pointer to a FTP server callback data table. */
Index: /trunk/src/VBox/Runtime/generic/ftp-server.cpp
===================================================================
--- /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82714)
+++ /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82715)
@@ -40,8 +40,11 @@
 *********************************************************************************************************************************/
 #define LOG_GROUP RTLOGGROUP_FTP
+#include <iprt/ftp.h>
+#include "internal/iprt.h"
+#include "internal/magics.h"
+
 #include <iprt/asm.h>
 #include <iprt/assert.h>
 #include <iprt/errcore.h>
-#include <iprt/ftp.h>
 #include <iprt/getopt.h>
 #include <iprt/mem.h>
@@ -53,6 +56,4 @@
 #include <iprt/system.h>
 #include <iprt/tcp.h>
-
-#include "internal/magics.h"
 
 
@@ -172,4 +173,34 @@
             return pCallbacks->a_Name(&Data); \
         } \
+        else \
+            return VERR_NOT_IMPLEMENTED; \
+    } while (0)
+
+/** Handles a FTP server callback with no arguments and sets rc accordingly. */
+#define RTFTPSERVER_HANDLE_CALLBACK(a_Name) \
+    do \
+    { \
+        PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+        if (pCallbacks->a_Name) \
+        { \
+            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            rc = pCallbacks->a_Name(&Data); \
+        } \
+        else \
+            rc = VERR_NOT_IMPLEMENTED; \
+    } while (0)
+
+/** Handles a FTP server callback with arguments and sets rc accordingly. */
+#define RTFTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
+    do \
+    { \
+        PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+        if (pCallbacks->a_Name) \
+        { \
+            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
+        } \
+        else \
+            rc = VERR_NOT_IMPLEMENTED; \
     } while (0)
 
@@ -184,4 +215,6 @@
             return pCallbacks->a_Name(&Data, __VA_ARGS__); \
         } \
+        else \
+            return VERR_NOT_IMPLEMENTED; \
     } while (0)
 
@@ -323,8 +356,59 @@
 
     /** @todo Anything to do here? */
-    return VINF_SUCCESS;
+    return VERR_NOT_IMPLEMENTED;
 }
 
 static int rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+{
+    RT_NOREF(cArgs, apcszArgs);
+
+    int rc;
+
+    RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp);
+
+    if (RT_SUCCESS(rc))
+        return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
+
+    return rc;
+}
+
+static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+{
+    if (cArgs != 1)
+        return VERR_INVALID_PARAMETER;
+
+    int rc;
+
+    RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, apcszArgs[0]);
+
+    if (RT_SUCCESS(rc))
+        return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
+
+    return rc;
+}
+
+static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+{
+    RT_NOREF(cArgs, apcszArgs);
+
+    int rc;
+
+    void   *pvData = NULL;
+    size_t  cbData = 0;
+
+    /* The first argument might indicate a directory to list. */
+    RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnList,
+                                     cArgs == 1
+                                   ? apcszArgs[0] : NULL, &pvData, &cbData);
+
+    if (RT_SUCCESS(rc))
+    {
+        RTMemFree(pvData);
+    }
+
+    return rc;
+}
+
+static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
 {
     RT_NOREF(pClient, cArgs, apcszArgs);
@@ -334,42 +418,12 @@
 }
 
-static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
-{
-    AssertPtrReturn(apcszArgs, VERR_INVALID_POINTER);
-
-    if (cArgs != 1)
-        return VERR_INVALID_PARAMETER;
-
-    RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnPathSetCurrent, apcszArgs[0]);
-
-    return VERR_NOT_IMPLEMENTED;
-}
-
-static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
+static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
 {
     RT_NOREF(cArgs, apcszArgs);
 
-    void   *pvData = NULL;
-    size_t  cbData = 0;
-
-    RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnList, &pvData, &cbData);
-
-    return VERR_NOT_IMPLEMENTED;
-}
-
-static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
-{
-    RT_NOREF(pClient, cArgs, apcszArgs);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
-{
-    RT_NOREF(pClient, cArgs, apcszArgs);
-
-    /* Nothing to do here. */
-    return VINF_SUCCESS;
+    /* Save timestamp of last command sent. */
+    pClient->State.tsLastCmdMs = RTTimeMilliTS();
+
+    return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
 }
 
@@ -404,5 +458,5 @@
 
     /** @todo Anything to do here? */
-    return VINF_SUCCESS;
+    return VERR_NOT_IMPLEMENTED;
 }
 
@@ -411,19 +465,14 @@
     RT_NOREF(cArgs, apcszArgs);
 
-#if 0
-    char *pszReply;
-    int rc = RTStrAPrintf(&pszReply, "%s\r\n", pClient->szCWD);
-    if (RT_SUCCESS(rc))
-    {
-        rc = RTTcpWrite(pClient->hSocket, pszReply, strlen(pszReply) + 1);
-        RTStrFree(pszReply);
-        return rc;
-    }
-
-    return VERR_NO_MEMORY;
-#endif
-
-    RT_NOREF(pClient);
-    return 0;
+    int rc;
+
+    char szPWD[RTPATH_MAX];
+
+    RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
+
+    if (RT_SUCCESS(rc))
+       rc = rtFtpServerSendReplyStr(pClient, szPWD);
+
+    return rc;
 }
 
@@ -433,5 +482,5 @@
 
     /** @todo Anything to do here? */
-    return VINF_SUCCESS;
+    return VERR_NOT_IMPLEMENTED;
 }
 
@@ -441,5 +490,5 @@
 
     /** @todo Anything to do here? */
-    return VINF_SUCCESS;
+    return VERR_NOT_IMPLEMENTED;
 }
 
@@ -449,5 +498,5 @@
 
     /** @todo Anything to do here? */
-    return VINF_SUCCESS;
+    return VERR_NOT_IMPLEMENTED;
 }
 
@@ -457,5 +506,5 @@
 
     /** @todo Anything to do here? */
-    return VINF_SUCCESS;
+    return VERR_NOT_IMPLEMENTED;
 }
 
@@ -477,5 +526,5 @@
 
     /** @todo Anything to do here? */
-    return VINF_SUCCESS;
+    return VERR_NOT_IMPLEMENTED;
 }
 
@@ -483,5 +532,5 @@
 {
     if (cArgs != 1)
-        return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
+        return VERR_INVALID_PARAMETER;
 
     const char *pcszUser = apcszArgs[0];
@@ -591,4 +640,6 @@
             if (pszCmdEnd)
                 *pszCmdEnd = '\0';
+
+            int rcCmd = VINF_SUCCESS;
 
             uint8_t cArgs     = 0;
@@ -606,5 +657,5 @@
                         pClient->State.tsLastCmdMs = RTTimeMilliTS();
 
-                        rc = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
+                        rcCmd = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
                         break;
                     }
@@ -618,4 +669,6 @@
                     if (RT_SUCCESS(rc))
                         rc = rc2;
+
+                    continue;
                 }
 
@@ -630,4 +683,20 @@
                     RTFTPSERVER_HANDLE_CALLBACK_RET(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;
                 }
             }
@@ -717,4 +786,8 @@
         rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
                                rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
+        if (RT_SUCCESS(rc))
+        {
+            *phFTPServer = (RTFTPSERVER)pThis;
+        }
     }
     else
Index: /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp
===================================================================
--- /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82714)
+++ /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82715)
@@ -23,4 +23,13 @@
  * You may elect to license modified versions of this file under the
  * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+/*
+ * Use this setup to best see what's going on:
+ *
+ *    VBOX_LOG=rt_ftp=~0
+ *    VBOX_LOG_DEST="nofile stderr"
+ *    VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
+ *
  */
 
@@ -54,9 +63,20 @@
 
 /*********************************************************************************************************************************
+*   Definitations                                                                                                                *
+*********************************************************************************************************************************/
+typedef struct FTPSERVERDATA
+{
+    char szRootDir[RTPATH_MAX];
+    char szCWD[RTPATH_MAX];
+} FTPSERVERDATA;
+typedef FTPSERVERDATA *PFTPSERVERDATA;
+
+
+/*********************************************************************************************************************************
 *   Global Variables                                                                                                             *
 *********************************************************************************************************************************/
 /** Set by the signal handler when the FTP server shall be terminated. */
 static volatile bool  g_fCanceled  = false;
-static char          *g_pszRootDir = NULL;
+static FTPSERVERDATA  g_FTPServerData;
 
 
@@ -150,5 +170,5 @@
     RT_NOREF(pData, pcszUser);
 
-    RTPrintf("User '%s' connected", pcszUser);
+    RTPrintf("User '%s' connected\n", pcszUser);
 
     return VINF_SUCCESS;
@@ -159,5 +179,5 @@
     RT_NOREF(pData, pcszUser, pcszPassword);
 
-    RTPrintf("Authenticating user '%s' ...", pcszUser);
+    RTPrintf("Authenticating user '%s' ...\n", pcszUser);
 
     return VINF_SUCCESS;
@@ -168,5 +188,5 @@
     RT_NOREF(pData);
 
-    RTPrintf("User disconnected");
+    RTPrintf("User disconnected\n");
 
     return VINF_SUCCESS;
@@ -175,21 +195,36 @@
 static DECLCALLBACK(int) onPathSetCurrent(PRTFTPCALLBACKDATA pData, const char *pcszCWD)
 {
-    RT_NOREF(pData, pcszCWD);
+    PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+    Assert(pData->cbUser == sizeof(FTPSERVERDATA));
 
     RTPrintf("Setting current directory to '%s'\n", pcszCWD);
 
-    return VINF_SUCCESS;
+    /** @todo BUGBUG Santiy checks! */
+
+    return RTStrCopy(pThis->szCWD, sizeof(pThis->szCWD), pcszCWD);
 }
 
 static DECLCALLBACK(int) onPathGetCurrent(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD)
 {
-    RT_NOREF(pData, pszPWD, cbPWD);
-
-    return VINF_SUCCESS;
-}
-
-static DECLCALLBACK(int) onList(PRTFTPCALLBACKDATA pData, void **ppvData, size_t *pcbData)
-{
-    RT_NOREF(pData, ppvData, pcbData);
+    PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
+    Assert(pData->cbUser == sizeof(FTPSERVERDATA));
+
+    RTPrintf("Current directory is: '%s'\n", pThis->szCWD);
+
+    RTStrPrintf(pszPWD, cbPWD, "%s", pThis->szCWD);
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onPathUp(PRTFTPCALLBACKDATA pData)
+{
+    RT_NOREF(pData);
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onList(PRTFTPCALLBACKDATA pData, const char *pcszPath, void **ppvData, size_t *pcbData)
+{
+    RT_NOREF(pData, pcszPath, ppvData, pcbData);
 
     return VINF_SUCCESS;
@@ -203,6 +238,8 @@
 
     /* Use some sane defaults. */
-    char     szAddress[64]         = "localhost";
-    uint16_t uPort                 = 2121;
+    char     szAddress[64] = "localhost";
+    uint16_t uPort         = 2121;
+
+    RT_ZERO(g_FTPServerData);
 
     /*
@@ -238,5 +275,5 @@
 
             case 'r':
-                g_pszRootDir = RTStrDup(ValueUnion.psz);
+                RTStrCopy(g_FTPServerData.szRootDir, sizeof(g_FTPServerData.szRootDir), ValueUnion.psz);
                 break;
 
@@ -273,17 +310,14 @@
     }
 
-    if (!g_pszRootDir)
-    {
-        char szRootDir[RTPATH_MAX];
-
+    if (!strlen(g_FTPServerData.szRootDir))
+    {
         /* By default use the current directory as serving root directory. */
-        rc = RTPathGetCurrent(szRootDir, sizeof(szRootDir));
+        rc = RTPathGetCurrent(g_FTPServerData.szRootDir, sizeof(g_FTPServerData.szRootDir));
         if (RT_FAILURE(rc))
             return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
-
-        g_pszRootDir = RTStrDup(szRootDir);
-        if (!g_pszRootDir)
-            return RTMsgErrorExit(RTEXITCODE_FAILURE, "Allocating current directory failed");
-    }
+    }
+
+    /* Initialize CWD. */
+    RTStrPrintf2(g_FTPServerData.szCWD, sizeof(g_FTPServerData.szCWD), "/");
 
     /* Install signal handler. */
@@ -296,4 +330,8 @@
         RTFTPSERVERCALLBACKS Callbacks;
         RT_ZERO(Callbacks);
+
+        Callbacks.pvUser                = &g_FTPServerData;
+        Callbacks.cbUser                = sizeof(g_FTPServerData);
+
         Callbacks.pfnOnUserConnect      = onUserConnect;
         Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
@@ -301,4 +339,6 @@
         Callbacks.pfnOnPathSetCurrent   = onPathSetCurrent;
         Callbacks.pfnOnPathGetCurrent   = onPathGetCurrent;
+        Callbacks.pfnOnPathUp           = onPathUp;
+        Callbacks.pfnOnList             = onList;
         Callbacks.pfnOnList             = onList;
 
@@ -308,5 +348,5 @@
         {
             RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
-            RTPrintf("Root directory is '%s'\n", g_pszRootDir);
+            RTPrintf("Root directory is '%s'\n", g_FTPServerData.szRootDir);
 
             RTPrintf("Running FTP server ...\n");
@@ -336,6 +376,4 @@
     }
 
-    RTStrFree(g_pszRootDir);
-
     /* Set rcExit on failure in case we forgot to do so before. */
     if (RT_FAILURE(rc))
