Index: /trunk/include/iprt/ftp.h
===================================================================
--- /trunk/include/iprt/ftp.h	(revision 82686)
+++ /trunk/include/iprt/ftp.h	(revision 82687)
@@ -133,4 +133,51 @@
 
 /**
+ * Structure for maintaining a FTP server client state.
+ */
+typedef struct RTFTPSERVERCLIENTSTATE
+{
+    /** Timestamp (in ms) of last command issued by the client. */
+    uint64_t      tsLastCmdMs;
+} RTFTPSERVERCLIENTSTATE;
+/** Pointer to a FTP server client state. */
+typedef RTFTPSERVERCLIENTSTATE *PRTFTPSERVERCLIENTSTATE;
+
+/**
+ * Structure for storing FTP server callback data.
+ */
+typedef struct RTFTPCALLBACKDATA
+{
+    /** Pointer to the client state. */
+    PRTFTPSERVERCLIENTSTATE  pClient;
+    /** Saved user pointer. */
+    void                    *pvUser;
+    /** Size (in bytes) of data at user pointer. */
+    size_t                   cbUser;
+} RTFTPCALLBACKDATA;
+/** Pointer to FTP server callback data. */
+typedef RTFTPCALLBACKDATA *PRTFTPCALLBACKDATA;
+
+/**
+ * Function callback table for the FTP server implementation.
+ *
+ * All callbacks are optional and therefore can be NULL.
+ */
+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;
+    DECLCALLBACKMEMBER(int,  pfnOnUserConnect)(PRTFTPCALLBACKDATA pData, const char *pcszUser);
+    DECLCALLBACKMEMBER(int,  pfnOnUserAuthenticate)(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword);
+    DECLCALLBACKMEMBER(int,  pfnOnUserDisconnect)(PRTFTPCALLBACKDATA pData);
+    DECLCALLBACKMEMBER(int,  pfnOnPathSetCurrent)(PRTFTPCALLBACKDATA pData, const char *pcszCWD);
+    DECLCALLBACKMEMBER(int,  pfnOnPathGetCurrent)(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD);
+    DECLCALLBACKMEMBER(int,  pfnOnList)(PRTFTPCALLBACKDATA pData, void **ppvData, size_t *pcbData);
+} RTFTPSERVERCALLBACKS, *PRTFTPSERVERCALLBACKS;
+/** Pointer to a FTP server callback data table. */
+typedef RTFTPSERVERCALLBACKS *PRTFTPSERVERCALLBACKS;
+
+/**
  * Creates a FTP server instance.
  *
@@ -140,8 +187,8 @@
  *                              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.
+ * @param   pCallbacks          Callback table to use.
  */
 RTR3DECL(int) RTFTPServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
-                                const char *pcszPathRoot);
+                                PRTFTPSERVERCALLBACKS pCallbacks);
 
 /**
Index: /trunk/src/VBox/Runtime/generic/ftp-server.cpp
===================================================================
--- /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82686)
+++ /trunk/src/VBox/Runtime/generic/ftp-server.cpp	(revision 82687)
@@ -40,4 +40,5 @@
 *********************************************************************************************************************************/
 #define LOG_GROUP RTLOGGROUP_FTP
+#include <iprt/asm.h>
 #include <iprt/assert.h>
 #include <iprt/errcore.h>
@@ -45,4 +46,5 @@
 #include <iprt/mem.h>
 #include <iprt/log.h>
+#include <iprt/path.h>
 #include <iprt/poll.h>
 #include <iprt/socket.h>
@@ -63,7 +65,11 @@
 {
     /** Magic value. */
-    uint32_t            u32Magic;
+    uint32_t                u32Magic;
+    /** Callback table. */
+    RTFTPSERVERCALLBACKS    Callbacks;
     /** Pointer to TCP server instance. */
-    PRTTCPSERVER        pTCPServer;
+    PRTTCPSERVER            pTCPServer;
+    /** Number of currently connected clients. */
+    uint32_t                cClients;
 } RTFTPSERVERINTERNAL;
 /** Pointer to an internal FTP server instance. */
@@ -136,20 +142,46 @@
 
 /**
- * Structure for maintaining an internal FTP server client state.
- */
-typedef struct RTFTPSERVERCLIENTSTATE
+ * Structure for maintaining an internal FTP server client.
+ */
+typedef struct RTFTPSERVERCLIENT
 {
     /** Pointer to internal server state. */
-    PRTFTPSERVERINTERNAL pServer;
+    PRTFTPSERVERINTERNAL        pServer;
     /** Socket handle the client is bound to. */
-    RTSOCKET             hSocket;
-} RTFTPSERVERCLIENTSTATE;
+    RTSOCKET                    hSocket;
+    /** Actual client state. */
+    RTFTPSERVERCLIENTSTATE      State;
+} RTFTPSERVERCLIENT;
 /** Pointer to an internal FTP server client state. */
-typedef RTFTPSERVERCLIENTSTATE *PRTFTPSERVERCLIENTSTATE;
+typedef RTFTPSERVERCLIENT *PRTFTPSERVERCLIENT;
 
 /** Function pointer declaration for a specific FTP server command handler. */
-typedef DECLCALLBACK(int) FNRTFTPSERVERCMD(PRTFTPSERVERCLIENTSTATE pClient);
+typedef DECLCALLBACK(int) FNRTFTPSERVERCMD(PRTFTPSERVERCLIENT pClient);
 /** Pointer to a FNRTFTPSERVERCMD(). */
 typedef FNRTFTPSERVERCMD *PFNRTFTPSERVERCMD;
+
+/** Handles a FTP server callback with no arguments and returns. */
+#define RTFTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
+    do \
+    { \
+        PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+        if (pCallbacks->a_Name) \
+        { \
+            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            return pCallbacks->a_Name(&Data); \
+        } \
+    } while (0)
+
+/** Handles a FTP server callback with arguments and returns. */
+#define RTFTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
+    do \
+    { \
+        PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
+        if (pCallbacks->a_Name) \
+        { \
+            RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
+            return pCallbacks->a_Name(&Data, __VA_ARGS__); \
+        } \
+    } while (0)
 
 /**
@@ -207,5 +239,16 @@
 
 
-static int rtFTPServerSendReplyRc(PRTFTPSERVERCLIENTSTATE pClient, RTFTPSERVER_REPLY enmReply)
+/*********************************************************************************************************************************
+*   Protocol Functions                                                                                                           *
+*********************************************************************************************************************************/
+
+/**
+ * Replies a (three digit) reply code back to the client.
+ *
+ * @returns VBox status code.
+ * @param   pClient             Client to reply to.
+ * @param   enmReply            Reply code to send.
+ */
+static int rtFTPServerSendReplyRc(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply)
 {
     char szReply[32];
@@ -215,5 +258,12 @@
 }
 
-static int rtFTPServerSendReplyStr(PRTFTPSERVERCLIENTSTATE pClient, const char *pcszStr)
+/**
+ * Replies a string back to the client.
+ *
+ * @returns VBox status code.
+ * @param   pClient             Client to reply to.
+ * @param   pcszStr             String to reply.
+ */
+static int rtFTPServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszStr)
 {
     char *pszReply;
@@ -229,119 +279,146 @@
 }
 
-static int rtFTPServerLookupUser(PRTFTPSERVERCLIENTSTATE pClient, const char *pcszUser)
-{
-    RT_NOREF(pClient, pcszUser);
-
-    LogFunc(("User='%s'\n", pcszUser));
-
-    return VINF_SUCCESS; /** @todo Implement lookup. */
-}
-
-static int rtFTPServerAuthenticate(PRTFTPSERVERCLIENTSTATE pClient, const char *pcszUser, const char *pcszPassword)
-{
-    RT_NOREF(pClient, pcszUser, pcszPassword);
-
-    LogFunc(("User='%s', Password='%s'\n", pcszUser, pcszPassword));
-
-    return VINF_SUCCESS; /** @todo Implement authentication. */
-}
-
-static int rtFTPServerHandleABOR(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleCDUP(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleCWD(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleLIST(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleMODE(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleNOOP(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandlePORT(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandlePWD(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleQUIT(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleRETR(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleRGET(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleSTAT(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerHandleSYST(PRTFTPSERVERCLIENTSTATE pClient)
+/**
+ * Looks up an user account.
+ *
+ * @returns VBox status code, or VERR_NOT_FOUND if user has not been found.
+ * @param   pClient             Client to look up user for.
+ * @param   pcszUser            User name to look up.
+ */
+static int rtFTPServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pcszUser)
+{
+    RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pcszUser);
+
+    return VERR_NOT_FOUND;
+}
+
+/**
+ * Handles the actual client authentication.
+ *
+ * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed.
+ * @param   pClient             Client to authenticate.
+ * @param   pcszUser            User name to authenticate with.
+ * @param   pcszPassword        Password to authenticate with.
+ */
+static int rtFTPServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pcszUser, const char *pcszPassword)
+{
+    RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pcszUser, pcszPassword);
+
+    return VERR_ACCESS_DENIED;
+}
+
+
+/*********************************************************************************************************************************
+*   Command Protocol Handlers                                                                                                    *
+*********************************************************************************************************************************/
+
+static int rtFTPServerHandleABOR(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleCDUP(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleCWD(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleLIST(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleMODE(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleNOOP(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /* Nothing to do here. */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandlePORT(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandlePWD(PRTFTPSERVERCLIENT pClient)
+{
+#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;
+}
+
+static int rtFTPServerHandleQUIT(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleRETR(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleRGET(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleSTAT(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+static int rtFTPServerHandleSYST(PRTFTPSERVERCLIENT pClient)
 {
     char szOSInfo[64];
@@ -353,13 +430,24 @@
 }
 
-static int rtFTPServerHandleTYPE(PRTFTPSERVERCLIENTSTATE pClient)
-{
-    RT_NOREF(pClient);
-
-    /** @todo Anything to do here? */
-    return VINF_SUCCESS;
-}
-
-static int rtFTPServerDoLogin(PRTFTPSERVERCLIENTSTATE pClient)
+static int rtFTPServerHandleTYPE(PRTFTPSERVERCLIENT pClient)
+{
+    RT_NOREF(pClient);
+
+    /** @todo Anything to do here? */
+    return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+*   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();
@@ -408,5 +496,11 @@
 }
 
-static int rtFTPServerProcessCommands(PRTFTPSERVERCLIENTSTATE pClient)
+/**
+ * Main loop for processing client commands.
+ *
+ * @returns VBox status code.
+ * @param   pClient             Client to process commands for.
+ */
+static int rtFTPServerProcessCommands(PRTFTPSERVERCLIENT pClient)
 {
     int rc;
@@ -417,7 +511,9 @@
         char   szCmd[RTFTPSERVER_MAX_CMD_LEN];
         rc = RTTcpRead(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
-        if (   RT_SUCCESS(rc)
-            && strlen(szCmd))
+        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);
@@ -439,4 +535,7 @@
                 if (!RTStrICmp(szCmd, g_aCmdMap[i].szCmd))
                 {
+                    /* Save timestamp of last command sent. */
+                    pClient->State.tsLastCmdMs = RTTimeMilliTS();
+
                     rc = g_aCmdMap[i].pfnCmd(pClient);
                     break;
@@ -452,5 +551,8 @@
 
             if (g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT)
+            {
+                RTFTPSERVER_HANDLE_CALLBACK_RET(pfnOnUserDisconnect);
                 break;
+            }
         }
         else
@@ -465,18 +567,43 @@
 }
 
-static DECLCALLBACK(int) rtFTPServerThread(RTSOCKET hSocket, void *pvUser)
+/**
+ * Resets the client's state.
+ *
+ * @param   pState              Client state to reset.
+ */
+static void rtFTPServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
+{
+    pState->tsLastCmdMs = RTTimeMilliTS();
+}
+
+/**
+ * Per-client thread for serving the server's control connection.
+ *
+ * @returns VBox status code.
+ * @param   hSocket             Socket handle to use for the control connection.
+ * @param   pvUser              User-provided arguments. Of type PRTFTPSERVERINTERNAL.
+ */
+static DECLCALLBACK(int) rtFTPServerClientThread(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);
+    RTFTPSERVERCLIENT Client;
+    RT_ZERO(Client);
+
+    Client.pServer     = pThis;
+    Client.hSocket     = hSocket;
+
+    rtFTPServerClientStateReset(&Client.State);
+
+    int rc = rtFTPServerDoLogin(&Client);
     if (RT_SUCCESS(rc))
-        rc = rtFTPServerProcessCommands(&ClientState);
+    {
+        ASMAtomicIncU32(&pThis->cClients);
+
+        rc = rtFTPServerProcessCommands(&Client);
+
+        ASMAtomicDecU32(&pThis->cClients);
+    }
 
     return rc;
@@ -484,10 +611,10 @@
 
 RTR3DECL(int) RTFTPServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
-                                const char *pcszPathRoot)
+                                PRTFTPSERVERCALLBACKS pCallbacks)
 {
     AssertPtrReturn(phFTPServer,  VERR_INVALID_POINTER);
     AssertPtrReturn(pcszAddress,  VERR_INVALID_POINTER);
-    AssertPtrReturn(pcszPathRoot, VERR_INVALID_POINTER);
     AssertReturn   (uPort,        VERR_INVALID_PARAMETER);
+    AssertPtrReturn(pCallbacks,   VERR_INVALID_POINTER);
 
     int rc;
@@ -496,8 +623,9 @@
     if (pThis)
     {
-        pThis->u32Magic = RTFTPSERVER_MAGIC;
+        pThis->u32Magic  = RTFTPSERVER_MAGIC;
+        pThis->Callbacks = *pCallbacks;
 
         rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
-                               rtFTPServerThread, pThis /* pvUser */, &pThis->pTCPServer);
+                               rtFTPServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
     }
     else
Index: /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp
===================================================================
--- /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82686)
+++ /trunk/src/VBox/Runtime/tools/RTFTPServer.cpp	(revision 82687)
@@ -57,5 +57,6 @@
 *********************************************************************************************************************************/
 /** Set by the signal handler when the FTP server shall be terminated. */
-static volatile bool g_fCanceled = false;
+static volatile bool  g_fCanceled  = false;
+static char          *g_pszRootDir = NULL;
 
 
@@ -145,4 +146,54 @@
 }
 
+static DECLCALLBACK(int) onUserConnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
+{
+    RT_NOREF(pData, pcszUser);
+
+    RTPrintf("User '%s' connected", pcszUser);
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onUserAuthenticate(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword)
+{
+    RT_NOREF(pData, pcszUser, pcszPassword);
+
+    RTPrintf("Authenticating user '%s' ...", pcszUser);
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData)
+{
+    RT_NOREF(pData);
+
+    RTPrintf("User disconnected");
+
+    return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) onPathSetCurrent(PRTFTPCALLBACKDATA pData, const char *pcszCWD)
+{
+    RT_NOREF(pData, pcszCWD);
+
+    RTPrintf("Setting current directory to '%s'\n", pcszCWD);
+
+    return VINF_SUCCESS;
+}
+
+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);
+
+    return VINF_SUCCESS;
+}
+
 int main(int argc, char **argv)
 {
@@ -154,10 +205,4 @@
     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);
 
     /*
@@ -190,4 +235,8 @@
             case 'p':
                 uPort = ValueUnion.u16;
+                break;
+
+            case 'r':
+                g_pszRootDir = RTStrDup(ValueUnion.psz);
                 break;
 
@@ -224,4 +273,18 @@
     }
 
+    if (!g_pszRootDir)
+    {
+        char szRootDir[RTPATH_MAX];
+
+        /* By default use the current directory as serving root directory. */
+        rc = RTPathGetCurrent(szRootDir, sizeof(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");
+    }
+
     /* Install signal handler. */
     rc = signalHandlerInstall();
@@ -231,10 +294,19 @@
          * Create the FTP server instance.
          */
+        RTFTPSERVERCALLBACKS Callbacks;
+        RT_ZERO(Callbacks);
+        Callbacks.pfnOnUserConnect      = onUserConnect;
+        Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
+        Callbacks.pfnOnUserDisconnect   = onUserDisonnect;
+        Callbacks.pfnOnPathSetCurrent   = onPathSetCurrent;
+        Callbacks.pfnOnPathGetCurrent   = onPathGetCurrent;
+        Callbacks.pfnOnList             = onList;
+
         RTFTPSERVER hFTPServer;
-        rc = RTFTPServerCreate(&hFTPServer, szAddress, uPort, szRootDir);
+        rc = RTFTPServerCreate(&hFTPServer, szAddress, uPort, &Callbacks);
         if (RT_SUCCESS(rc))
         {
             RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
-            RTPrintf("Root directory is '%s'\n", szRootDir);
+            RTPrintf("Root directory is '%s'\n", g_pszRootDir);
 
             RTPrintf("Running FTP server ...\n");
@@ -264,4 +336,6 @@
     }
 
+    RTStrFree(g_pszRootDir);
+
     /* Set rcExit on failure in case we forgot to do so before. */
     if (RT_FAILURE(rc))
