VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTFtpServer.cpp@ 103914

Last change on this file since 103914 was 98103, checked in by vboxsync, 22 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.9 KB
Line 
1/* $Id: RTFtpServer.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * IPRT - Utility for running a (simple) FTP server.
4 *
5 * Use this setup to best see what's going on:
6 * VBOX_LOG=rt_ftp=~0
7 * VBOX_LOG_DEST="nofile stderr"
8 * VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
9 *
10 */
11
12/*
13 * Copyright (C) 2020-2023 Oracle and/or its affiliates.
14 *
15 * This file is part of VirtualBox base platform packages, as
16 * available from https://www.virtualbox.org.
17 *
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * as published by the Free Software Foundation, in version 3 of the
21 * License.
22 *
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, see <https://www.gnu.org/licenses>.
30 *
31 * The contents of this file may alternatively be used under the terms
32 * of the Common Development and Distribution License Version 1.0
33 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
34 * in the VirtualBox distribution, in which case the provisions of the
35 * CDDL are applicable instead of those of the GPL.
36 *
37 * You may elect to license modified versions of this file under the
38 * terms and conditions of either the GPL or the CDDL or both.
39 *
40 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
41 */
42
43
44/*********************************************************************************************************************************
45* Header Files *
46*********************************************************************************************************************************/
47#include <signal.h>
48
49#include <iprt/ftp.h>
50
51#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
52
53#include <iprt/asm.h>
54#include <iprt/assert.h>
55#include <iprt/ctype.h>
56#include <iprt/err.h>
57#include <iprt/file.h>
58#include <iprt/getopt.h>
59#include <iprt/initterm.h>
60#include <iprt/mem.h>
61#include <iprt/message.h>
62#include <iprt/path.h>
63#include <iprt/stream.h>
64#include <iprt/string.h>
65#include <iprt/thread.h>
66#include <iprt/vfs.h>
67
68#ifdef RT_OS_WINDOWS
69# include <iprt/win/windows.h>
70#endif
71
72
73/*********************************************************************************************************************************
74* Definitations *
75*********************************************************************************************************************************/
76typedef struct FTPSERVERDATA
77{
78 /** The absolute path of the FTP server's root directory. */
79 char szPathRootAbs[RTPATH_MAX];
80 /** The relative current working directory (CWD) to szRootDir. */
81 char szCWD[RTPATH_MAX];
82} FTPSERVERDATA;
83typedef FTPSERVERDATA *PFTPSERVERDATA;
84
85/**
86 * Enumeration specifying the VFS handle type of the FTP server.
87 */
88typedef enum FTPSERVERVFSHANDLETYPE
89{
90 FTPSERVERVFSHANDLETYPE_INVALID = 0,
91 FTPSERVERVFSHANDLETYPE_FILE,
92 FTPSERVERVFSHANDLETYPE_DIR,
93 /** The usual 32-bit hack. */
94 FTPSERVERVFSHANDLETYPE_32BIT_HACK = 0x7fffffff
95} FTPSERVERVFSHANDLETYPE;
96
97/**
98 * Structure for keeping a VFS handle of the FTP server.
99 */
100typedef struct FTPSERVERVFSHANDLE
101{
102 /** The type of the handle, stored in the union below. */
103 FTPSERVERVFSHANDLETYPE enmType;
104 union
105 {
106 /** The VFS (chain) handle to use for this file. */
107 RTVFSFILE hVfsFile;
108 /** The VFS (chain) handle to use for this directory. */
109 RTVFSDIR hVfsDir;
110 } u;
111} FTPSERVERVFSHANDLE;
112typedef FTPSERVERVFSHANDLE *PFTPSERVERVFSHANDLE;
113
114
115/*********************************************************************************************************************************
116* Global Variables *
117*********************************************************************************************************************************/
118/** Set by the signal handler when the FTP server shall be terminated. */
119static volatile bool g_fCanceled = false;
120static FTPSERVERDATA g_FtpServerData;
121
122
123#ifdef RT_OS_WINDOWS
124static BOOL WINAPI signalHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
125{
126 bool fEventHandled = FALSE;
127 switch (dwCtrlType)
128 {
129 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
130 * via GenerateConsoleCtrlEvent(). */
131 case CTRL_BREAK_EVENT:
132 case CTRL_CLOSE_EVENT:
133 case CTRL_C_EVENT:
134 ASMAtomicWriteBool(&g_fCanceled, true);
135 fEventHandled = TRUE;
136 break;
137 default:
138 break;
139 /** @todo Add other events here. */
140 }
141
142 return fEventHandled;
143}
144#else /* !RT_OS_WINDOWS */
145/**
146 * Signal handler that sets g_fCanceled.
147 *
148 * This can be executed on any thread in the process, on Windows it may even be
149 * a thread dedicated to delivering this signal. Don't do anything
150 * unnecessary here.
151 */
152static void signalHandler(int iSignal) RT_NOTHROW_DEF
153{
154 NOREF(iSignal);
155 ASMAtomicWriteBool(&g_fCanceled, true);
156}
157#endif
158
159/**
160 * Installs a custom signal handler to get notified
161 * whenever the user wants to intercept the program.
162 *
163 * @todo Make this handler available for all VBoxManage modules?
164 */
165static int signalHandlerInstall(void)
166{
167 g_fCanceled = false;
168
169 int rc = VINF_SUCCESS;
170#ifdef RT_OS_WINDOWS
171 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
172 {
173 rc = RTErrConvertFromWin32(GetLastError());
174 RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
175 }
176#else
177 signal(SIGINT, signalHandler);
178 signal(SIGTERM, signalHandler);
179# ifdef SIGBREAK
180 signal(SIGBREAK, signalHandler);
181# endif
182#endif
183 return rc;
184}
185
186/**
187 * Uninstalls a previously installed signal handler.
188 */
189static int signalHandlerUninstall(void)
190{
191 int rc = VINF_SUCCESS;
192#ifdef RT_OS_WINDOWS
193 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
194 {
195 rc = RTErrConvertFromWin32(GetLastError());
196 RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
197 }
198#else
199 signal(SIGINT, SIG_DFL);
200 signal(SIGTERM, SIG_DFL);
201# ifdef SIGBREAK
202 signal(SIGBREAK, SIG_DFL);
203# endif
204#endif
205 return rc;
206}
207
208static DECLCALLBACK(int) onUserConnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
209{
210 RT_NOREF(pData, pcszUser);
211
212 RTPrintf("User '%s' connected\n", pcszUser);
213
214 return VINF_SUCCESS;
215}
216
217static DECLCALLBACK(int) onUserAuthenticate(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword)
218{
219 RT_NOREF(pData, pcszUser, pcszPassword);
220
221 RTPrintf("Authenticating user '%s' ...\n", pcszUser);
222
223 return VINF_SUCCESS;
224}
225
226static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
227{
228 RT_NOREF(pData);
229
230 RTPrintf("User '%s' disconnected\n", pcszUser);
231
232 return VINF_SUCCESS;
233}
234
235static DECLCALLBACK(int) onFileOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle)
236{
237 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
238 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
239
240 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
241 if (!pHandle)
242 return VERR_NO_MEMORY;
243
244 char *pszPathAbs = NULL;
245 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
246 return VERR_NO_MEMORY;
247
248 int rc = RTVfsChainOpenFile(pszPathAbs, fMode, &pHandle->u.hVfsFile, NULL /*poffError */, NULL /* pErrInfo */);
249 if (RT_SUCCESS(rc))
250 {
251 pHandle->enmType = FTPSERVERVFSHANDLETYPE_FILE;
252
253 *ppvHandle = pHandle;
254 }
255 else
256 RTMemFree(pHandle);
257
258 RTStrFree(pszPathAbs);
259
260 return rc;
261}
262
263static DECLCALLBACK(int) onFileRead(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead)
264{
265 RT_NOREF(pData);
266
267 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
268 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
269 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
270
271 return RTVfsFileRead(pHandle->u.hVfsFile, pvBuf, cbToRead, pcbRead);
272}
273
274static DECLCALLBACK(int) onFileClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
275{
276 RT_NOREF(pData);
277
278 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
279 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
280 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_FILE, VERR_INVALID_PARAMETER);
281
282 int rc = RTVfsFileRelease(pHandle->u.hVfsFile);
283 if (RT_SUCCESS(rc))
284 {
285 RTMemFree(pvHandle);
286 pvHandle = NULL;
287 }
288
289 return rc;
290}
291
292static DECLCALLBACK(int) onFileGetSize(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize)
293{
294 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
295 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
296
297 char *pszStat = NULL;
298 if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
299 return VERR_NO_MEMORY;
300
301 RTPrintf("Retrieving file size for '%s' ...\n", pcszPath);
302
303 RTFILE hFile;
304 int rc = RTFileOpen(&hFile, pcszPath,
305 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
306 if (RT_SUCCESS(rc))
307 {
308 rc = RTFileQuerySize(hFile, puSize);
309 if (RT_SUCCESS(rc))
310 RTPrintf("File size is: %RU64\n", *puSize);
311 RTFileClose(hFile);
312 }
313
314 RTStrFree(pszStat);
315
316 return rc;
317}
318
319static DECLCALLBACK(int) onFileStat(PRTFTPCALLBACKDATA pData, const char *pcszPath, PRTFSOBJINFO pFsObjInfo)
320{
321 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
322 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
323
324 char *pszStat = NULL;
325 if (RTStrAPrintf(&pszStat, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
326 return VERR_NO_MEMORY;
327
328 RTPrintf("Stat for '%s'\n", pszStat);
329
330 RTFILE hFile;
331 int rc = RTFileOpen(&hFile, pszStat,
332 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
333 if (RT_SUCCESS(rc))
334 {
335 RTFSOBJINFO fsObjInfo;
336 rc = RTFileQueryInfo(hFile, &fsObjInfo, RTFSOBJATTRADD_NOTHING);
337 if (RT_SUCCESS(rc))
338 {
339 if (pFsObjInfo)
340 *pFsObjInfo = fsObjInfo;
341 }
342
343 RTFileClose(hFile);
344 }
345
346 RTStrFree(pszStat);
347
348 return rc;
349}
350
351static DECLCALLBACK(int) onPathSetCurrent(PRTFTPCALLBACKDATA pData, const char *pcszCWD)
352{
353 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
354 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
355
356 RTPrintf("Setting current directory to '%s'\n", pcszCWD);
357
358 /** @todo BUGBUG Santiy checks! */
359
360 return RTStrCopy(pThis->szCWD, sizeof(pThis->szCWD), pcszCWD);
361}
362
363static DECLCALLBACK(int) onPathGetCurrent(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD)
364{
365 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
366 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
367
368 RTPrintf("Current directory is: '%s'\n", pThis->szCWD);
369
370 return RTStrCopy(pszPWD, cbPWD, pThis->szCWD);
371}
372
373static DECLCALLBACK(int) onPathUp(PRTFTPCALLBACKDATA pData)
374{
375 RT_NOREF(pData);
376
377 return VINF_SUCCESS;
378}
379
380static DECLCALLBACK(int) onDirOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, void **ppvHandle)
381{
382 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
383 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
384
385 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)RTMemAllocZ(sizeof(FTPSERVERVFSHANDLE));
386 if (!pHandle)
387 return VERR_NO_MEMORY;
388
389 /* Construct absolute path. */
390 char *pszPathAbs = NULL;
391 if (RTStrAPrintf(&pszPathAbs, "%s/%s", pThis->szPathRootAbs, pcszPath) <= 0)
392 return VERR_NO_MEMORY;
393
394 RTPrintf("Opening directory '%s'\n", pszPathAbs);
395
396 int rc = RTVfsChainOpenDir(pszPathAbs, 0 /*fFlags*/, &pHandle->u.hVfsDir, NULL /* poffError */, NULL /* pErrInfo */);
397 if (RT_SUCCESS(rc))
398 {
399 pHandle->enmType = FTPSERVERVFSHANDLETYPE_DIR;
400
401 *ppvHandle = pHandle;
402 }
403 else
404 {
405 RTMemFree(pHandle);
406 }
407
408 RTStrFree(pszPathAbs);
409
410 return rc;
411}
412
413static DECLCALLBACK(int) onDirClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
414{
415 RT_NOREF(pData);
416
417 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
418 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
419 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
420
421 RTVfsDirRelease(pHandle->u.hVfsDir);
422
423 RTMemFree(pHandle);
424 pHandle = NULL;
425
426 return VINF_SUCCESS;
427}
428
429static DECLCALLBACK(int) onDirRead(PRTFTPCALLBACKDATA pData, void *pvHandle, char **ppszEntry,
430 PRTFSOBJINFO pInfo, char **ppszOwner, char **ppszGroup, char **ppszTarget)
431{
432 RT_NOREF(pData);
433 RT_NOREF(ppszTarget); /* No symlinks yet */
434
435 PFTPSERVERVFSHANDLE pHandle = (PFTPSERVERVFSHANDLE)pvHandle;
436 AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
437 AssertReturn(pHandle->enmType == FTPSERVERVFSHANDLETYPE_DIR, VERR_INVALID_PARAMETER);
438
439 size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
440 PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
441 if (!pDirEntry)
442 return VERR_NO_MEMORY;
443
444 int rc;
445
446 for (;;)
447 {
448 size_t cbDirEntry = cbDirEntryAlloced;
449 rc = RTVfsDirReadEx(pHandle->u.hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
450 if (RT_FAILURE(rc))
451 {
452 if (rc == VERR_BUFFER_OVERFLOW)
453 {
454 RTMemTmpFree(pDirEntry);
455 cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
456 pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
457 if (pDirEntry)
458 continue;
459 }
460 else if (rc != VERR_NO_MORE_FILES)
461 break;
462 }
463
464 if (RT_SUCCESS(rc))
465 {
466 if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID)
467 {
468 RTFSOBJINFO OwnerInfo;
469 rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
470 pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
471 if ( RT_SUCCESS(rc)
472 && OwnerInfo.Attr.u.UnixOwner.szName[0])
473 {
474 *ppszOwner = RTStrDup(&OwnerInfo.Attr.u.UnixOwner.szName[0]);
475 if (!*ppszOwner)
476 rc = VERR_NO_MEMORY;
477 }
478 }
479
480 if ( RT_SUCCESS(rc)
481 && pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID)
482 {
483 RTFSOBJINFO GroupInfo;
484 rc = RTVfsDirQueryPathInfo(pHandle->u.hVfsDir,
485 pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
486 if ( RT_SUCCESS(rc)
487 && GroupInfo.Attr.u.UnixGroup.szName[0])
488 {
489 *ppszGroup = RTStrDup(&GroupInfo.Attr.u.UnixGroup.szName[0]);
490 if (!*ppszGroup)
491 rc = VERR_NO_MEMORY;
492 }
493 }
494 }
495
496 *ppszEntry = RTStrDup(pDirEntry->szName);
497 AssertPtrReturn(*ppszEntry, VERR_NO_MEMORY);
498
499 *pInfo = pDirEntry->Info;
500
501 break;
502
503 } /* for */
504
505 RTMemTmpFree(pDirEntry);
506 pDirEntry = NULL;
507
508 return rc;
509}
510
511int main(int argc, char **argv)
512{
513 int rc = RTR3InitExe(argc, &argv, 0);
514 if (RT_FAILURE(rc))
515 return RTMsgInitFailure(rc);
516
517 /* Use some sane defaults. */
518 char szAddress[64] = "localhost";
519 uint16_t uPort = 2121;
520
521 RT_ZERO(g_FtpServerData);
522
523 /*
524 * Parse arguments.
525 */
526 static const RTGETOPTDEF s_aOptions[] =
527 {
528 { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
529 /** @todo Implement IPv6 support? */
530 { "--port", 'p', RTGETOPT_REQ_UINT16 },
531 { "--root-dir", 'r', RTGETOPT_REQ_STRING },
532 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
533 };
534
535 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
536 unsigned uVerbosityLevel = 1;
537
538 RTGETOPTUNION ValueUnion;
539 RTGETOPTSTATE GetState;
540 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
541 while ((rc = RTGetOpt(&GetState, &ValueUnion)))
542 {
543 switch (rc)
544 {
545 case 'a':
546 RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
547 ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
548 break;
549
550 case 'p':
551 uPort = ValueUnion.u16;
552 break;
553
554 case 'r':
555 RTStrCopy(g_FtpServerData.szPathRootAbs, sizeof(g_FtpServerData.szPathRootAbs), ValueUnion.psz);
556 break;
557
558 case 'v':
559 uVerbosityLevel++;
560 break;
561
562 case 'h':
563 RTPrintf("Usage: %s [options]\n"
564 "\n"
565 "Options:\n"
566 " -a, --address (default: localhost)\n"
567 " Specifies the address to use for listening.\n"
568 " -p, --port (default: 2121)\n"
569 " Specifies the port to use for listening.\n"
570 " -r, --root-dir (default: current dir)\n"
571 " Specifies the root directory being served.\n"
572 " -v, --verbose\n"
573 " Controls the verbosity level.\n"
574 " -h, -?, --help\n"
575 " Display this help text and exit successfully.\n"
576 " -V, --version\n"
577 " Display the revision and exit successfully.\n"
578 , RTPathFilename(argv[0]));
579 return RTEXITCODE_SUCCESS;
580
581 case 'V':
582 RTPrintf("$Revision: 98103 $\n");
583 return RTEXITCODE_SUCCESS;
584
585 default:
586 return RTGetOptPrintError(rc, &ValueUnion);
587 }
588 }
589
590 if (!strlen(g_FtpServerData.szPathRootAbs))
591 {
592 /* By default use the current directory as serving root directory. */
593 rc = RTPathGetCurrent(g_FtpServerData.szPathRootAbs, sizeof(g_FtpServerData.szPathRootAbs));
594 if (RT_FAILURE(rc))
595 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
596 }
597
598 /* Initialize CWD. */
599 RTStrPrintf2(g_FtpServerData.szCWD, sizeof(g_FtpServerData.szCWD), "/");
600
601 /* Install signal handler. */
602 rc = signalHandlerInstall();
603 if (RT_SUCCESS(rc))
604 {
605 /*
606 * Create the FTP server instance.
607 */
608 RTFTPSERVERCALLBACKS Callbacks;
609 RT_ZERO(Callbacks);
610
611 Callbacks.pfnOnUserConnect = onUserConnect;
612 Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
613 Callbacks.pfnOnUserDisconnect = onUserDisonnect;
614 Callbacks.pfnOnFileOpen = onFileOpen;
615 Callbacks.pfnOnFileRead = onFileRead;
616 Callbacks.pfnOnFileClose = onFileClose;
617 Callbacks.pfnOnFileGetSize = onFileGetSize;
618 Callbacks.pfnOnFileStat = onFileStat;
619 Callbacks.pfnOnPathSetCurrent = onPathSetCurrent;
620 Callbacks.pfnOnPathGetCurrent = onPathGetCurrent;
621 Callbacks.pfnOnPathUp = onPathUp;
622 Callbacks.pfnOnDirOpen = onDirOpen;
623 Callbacks.pfnOnDirClose = onDirClose;
624 Callbacks.pfnOnDirRead = onDirRead;
625
626 RTFTPSERVER hFTPServer;
627 rc = RTFtpServerCreate(&hFTPServer, szAddress, uPort, &Callbacks,
628 &g_FtpServerData, sizeof(g_FtpServerData));
629 if (RT_SUCCESS(rc))
630 {
631 RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
632 RTPrintf("Root directory is '%s'\n", g_FtpServerData.szPathRootAbs);
633
634 RTPrintf("Running FTP server ...\n");
635
636 for (;;)
637 {
638 RTThreadSleep(200);
639
640 if (g_fCanceled)
641 break;
642 }
643
644 RTPrintf("Stopping FTP server ...\n");
645
646 int rc2 = RTFtpServerDestroy(hFTPServer);
647 if (RT_SUCCESS(rc))
648 rc = rc2;
649
650 RTPrintf("Stopped FTP server\n");
651 }
652 else
653 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFTPServerCreate failed: %Rrc", rc);
654
655 int rc2 = signalHandlerUninstall();
656 if (RT_SUCCESS(rc))
657 rc = rc2;
658 }
659
660 /* Set rcExit on failure in case we forgot to do so before. */
661 if (RT_FAILURE(rc))
662 rcExit = RTEXITCODE_FAILURE;
663
664 return rcExit;
665}
666
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette