VirtualBox

source: vbox/trunk/src/VBox/Runtime/tools/RTFTPServer.cpp@ 82749

Last change on this file since 82749 was 82732, checked in by vboxsync, 5 years ago

IPRT/FTP: More work on data connection handling. bugref:9646

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.3 KB
Line 
1/* $Id: RTFTPServer.cpp 82732 2020-01-14 09:53:20Z vboxsync $ */
2/** @file
3 * IPRT - Utility for running a (simple) FTP server.
4 */
5
6/*
7 * Copyright (C) 2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27/*
28 * Use this setup to best see what's going on:
29 *
30 * VBOX_LOG=rt_ftp=~0
31 * VBOX_LOG_DEST="nofile stderr"
32 * VBOX_LOG_FLAGS="unbuffered enabled thread msprog"
33 *
34 */
35
36
37/*********************************************************************************************************************************
38* Header Files *
39*********************************************************************************************************************************/
40#include <signal.h>
41
42#include <iprt/ftp.h>
43
44#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
45
46#include <iprt/asm.h>
47#include <iprt/assert.h>
48#include <iprt/ctype.h>
49#include <iprt/errcore.h>
50#include <iprt/file.h>
51#include <iprt/getopt.h>
52#include <iprt/initterm.h>
53#include <iprt/message.h>
54#include <iprt/path.h>
55#include <iprt/stream.h>
56#include <iprt/string.h>
57#include <iprt/thread.h>
58#include <iprt/vfs.h>
59
60#ifdef RT_OS_WINDOWS
61# include <iprt/win/windows.h>
62#endif
63
64
65/*********************************************************************************************************************************
66* Definitations *
67*********************************************************************************************************************************/
68typedef struct FTPSERVERDATA
69{
70 char szRootDir[RTPATH_MAX];
71 char szCWD[RTPATH_MAX];
72 RTFILE hFile;
73} FTPSERVERDATA;
74typedef FTPSERVERDATA *PFTPSERVERDATA;
75
76
77/*********************************************************************************************************************************
78* Global Variables *
79*********************************************************************************************************************************/
80/** Set by the signal handler when the FTP server shall be terminated. */
81static volatile bool g_fCanceled = false;
82static FTPSERVERDATA g_FTPServerData;
83
84
85#ifdef RT_OS_WINDOWS
86static BOOL WINAPI signalHandler(DWORD dwCtrlType)
87{
88 bool fEventHandled = FALSE;
89 switch (dwCtrlType)
90 {
91 /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
92 * via GenerateConsoleCtrlEvent(). */
93 case CTRL_BREAK_EVENT:
94 case CTRL_CLOSE_EVENT:
95 case CTRL_C_EVENT:
96 ASMAtomicWriteBool(&g_fCanceled, true);
97 fEventHandled = TRUE;
98 break;
99 default:
100 break;
101 /** @todo Add other events here. */
102 }
103
104 return fEventHandled;
105}
106#else /* !RT_OS_WINDOWS */
107/**
108 * Signal handler that sets g_fCanceled.
109 *
110 * This can be executed on any thread in the process, on Windows it may even be
111 * a thread dedicated to delivering this signal. Don't do anything
112 * unnecessary here.
113 */
114static void signalHandler(int iSignal)
115{
116 NOREF(iSignal);
117 ASMAtomicWriteBool(&g_fCanceled, true);
118}
119#endif
120
121/**
122 * Installs a custom signal handler to get notified
123 * whenever the user wants to intercept the program.
124 *
125 * @todo Make this handler available for all VBoxManage modules?
126 */
127static int signalHandlerInstall(void)
128{
129 g_fCanceled = false;
130
131 int rc = VINF_SUCCESS;
132#ifdef RT_OS_WINDOWS
133 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)signalHandler, TRUE /* Add handler */))
134 {
135 rc = RTErrConvertFromWin32(GetLastError());
136 RTMsgError("Unable to install console control handler, rc=%Rrc\n", rc);
137 }
138#else
139 signal(SIGINT, signalHandler);
140 signal(SIGTERM, signalHandler);
141# ifdef SIGBREAK
142 signal(SIGBREAK, signalHandler);
143# endif
144#endif
145 return rc;
146}
147
148/**
149 * Uninstalls a previously installed signal handler.
150 */
151static int signalHandlerUninstall(void)
152{
153 int rc = VINF_SUCCESS;
154#ifdef RT_OS_WINDOWS
155 if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
156 {
157 rc = RTErrConvertFromWin32(GetLastError());
158 RTMsgError("Unable to uninstall console control handler, rc=%Rrc\n", rc);
159 }
160#else
161 signal(SIGINT, SIG_DFL);
162 signal(SIGTERM, SIG_DFL);
163# ifdef SIGBREAK
164 signal(SIGBREAK, SIG_DFL);
165# endif
166#endif
167 return rc;
168}
169
170static DECLCALLBACK(int) onUserConnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
171{
172 RT_NOREF(pData, pcszUser);
173
174 RTPrintf("User '%s' connected\n", pcszUser);
175
176 return VINF_SUCCESS;
177}
178
179static DECLCALLBACK(int) onUserAuthenticate(PRTFTPCALLBACKDATA pData, const char *pcszUser, const char *pcszPassword)
180{
181 RT_NOREF(pData, pcszUser, pcszPassword);
182
183 RTPrintf("Authenticating user '%s' ...\n", pcszUser);
184
185 return VINF_SUCCESS;
186}
187
188static DECLCALLBACK(int) onUserDisonnect(PRTFTPCALLBACKDATA pData, const char *pcszUser)
189{
190 RT_NOREF(pData);
191
192 RTPrintf("User '%s' disconnected\n", pcszUser);
193
194 return VINF_SUCCESS;
195}
196
197static DECLCALLBACK(int) onFileOpen(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint32_t fMode, void **ppvHandle)
198{
199 RT_NOREF(ppvHandle);
200
201 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
202 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
203
204 return RTFileOpen(&pThis->hFile, pcszPath, fMode);
205}
206
207static DECLCALLBACK(int) onFileRead(PRTFTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbToRead, size_t *pcbRead)
208{
209 RT_NOREF(pvHandle);
210
211 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
212 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
213
214 return RTFileRead(pThis->hFile, pvBuf, cbToRead, pcbRead);
215}
216
217static DECLCALLBACK(int) onFileClose(PRTFTPCALLBACKDATA pData, void *pvHandle)
218{
219 RT_NOREF(pvHandle);
220
221 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
222 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
223
224 int rc = RTFileClose(pThis->hFile);
225 if (RT_SUCCESS(rc))
226 {
227 pThis->hFile = NIL_RTFILE;
228 }
229
230 return rc;
231}
232
233static DECLCALLBACK(int) onFileGetSize(PRTFTPCALLBACKDATA pData, const char *pcszPath, uint64_t *puSize)
234{
235 RT_NOREF(pData);
236
237 RTPrintf("Retrieving file size for '%s' ...\n", pcszPath);
238
239 RTFILE hFile;
240 int rc = RTFileOpen(&hFile, pcszPath,
241 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
242 if (RT_SUCCESS(rc))
243 {
244 rc = RTFileQuerySize(hFile, puSize);
245 if (RT_SUCCESS(rc))
246 RTPrintf("File size is: %RU64\n", *puSize);
247 RTFileClose(hFile);
248 }
249
250 return rc;
251}
252
253static DECLCALLBACK(int) onFileStat(PRTFTPCALLBACKDATA pData, const char *pcszPath, PRTFSOBJINFO pFsObjInfo)
254{
255 RT_NOREF(pData);
256
257 RTFILE hFile;
258 int rc = RTFileOpen(&hFile, pcszPath,
259 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
260 if (RT_SUCCESS(rc))
261 {
262 RTFSOBJINFO fsObjInfo;
263 rc = RTFileQueryInfo(hFile, &fsObjInfo, RTFSOBJATTRADD_NOTHING);
264 if (RT_SUCCESS(rc))
265 {
266 if (pFsObjInfo)
267 *pFsObjInfo = fsObjInfo;
268 }
269
270 RTFileClose(hFile);
271 }
272
273 return rc;
274}
275
276static DECLCALLBACK(int) onPathSetCurrent(PRTFTPCALLBACKDATA pData, const char *pcszCWD)
277{
278 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
279 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
280
281 RTPrintf("Setting current directory to '%s'\n", pcszCWD);
282
283 /** @todo BUGBUG Santiy checks! */
284
285 return RTStrCopy(pThis->szCWD, sizeof(pThis->szCWD), pcszCWD);
286}
287
288static DECLCALLBACK(int) onPathGetCurrent(PRTFTPCALLBACKDATA pData, char *pszPWD, size_t cbPWD)
289{
290 PFTPSERVERDATA pThis = (PFTPSERVERDATA)pData->pvUser;
291 Assert(pData->cbUser == sizeof(FTPSERVERDATA));
292
293 RTPrintf("Current directory is: '%s'\n", pThis->szCWD);
294
295 RTStrPrintf(pszPWD, cbPWD, "%s", pThis->szCWD);
296
297 return VINF_SUCCESS;
298}
299
300static DECLCALLBACK(int) onPathUp(PRTFTPCALLBACKDATA pData)
301{
302 RT_NOREF(pData);
303
304 return VINF_SUCCESS;
305}
306
307static DECLCALLBACK(int) onList(PRTFTPCALLBACKDATA pData, const char *pcszPath, void **ppvData, size_t *pcbData)
308{
309 RT_NOREF(pData, pcszPath, ppvData, pcbData);
310
311 return VINF_SUCCESS;
312}
313
314int main(int argc, char **argv)
315{
316 int rc = RTR3InitExe(argc, &argv, 0);
317 if (RT_FAILURE(rc))
318 return RTMsgInitFailure(rc);
319
320 /* Use some sane defaults. */
321 char szAddress[64] = "localhost";
322 uint16_t uPort = 2121;
323
324 RT_ZERO(g_FTPServerData);
325
326 /*
327 * Parse arguments.
328 */
329 static const RTGETOPTDEF s_aOptions[] =
330 {
331 { "--address", 'a', RTGETOPT_REQ_IPV4ADDR }, /** @todo Use a string for DNS hostnames? */
332 /** @todo Implement IPv6 support? */
333 { "--port", 'p', RTGETOPT_REQ_UINT16 },
334 { "--root-dir", 'r', RTGETOPT_REQ_STRING },
335 { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
336 };
337
338 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
339 unsigned uVerbosityLevel = 1;
340
341 RTGETOPTUNION ValueUnion;
342 RTGETOPTSTATE GetState;
343 RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
344 while ((rc = RTGetOpt(&GetState, &ValueUnion)))
345 {
346 switch (rc)
347 {
348 case 'a':
349 RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8", /** @todo Improve this. */
350 ValueUnion.IPv4Addr.au8[0], ValueUnion.IPv4Addr.au8[1], ValueUnion.IPv4Addr.au8[2], ValueUnion.IPv4Addr.au8[3]);
351 break;
352
353 case 'p':
354 uPort = ValueUnion.u16;
355 break;
356
357 case 'r':
358 RTStrCopy(g_FTPServerData.szRootDir, sizeof(g_FTPServerData.szRootDir), ValueUnion.psz);
359 break;
360
361 case 'v':
362 uVerbosityLevel++;
363 break;
364
365 case 'h':
366 RTPrintf("Usage: %s [options]\n"
367 "\n"
368 "Options:\n"
369 " -a, --address (default: localhost)\n"
370 " Specifies the address to use for listening.\n"
371 " -p, --port (default: 2121)\n"
372 " Specifies the port to use for listening.\n"
373 " -r, --root-dir (default: current dir)\n"
374 " Specifies the root directory being served.\n"
375 " -v, --verbose\n"
376 " Controls the verbosity level.\n"
377 " -h, -?, --help\n"
378 " Display this help text and exit successfully.\n"
379 " -V, --version\n"
380 " Display the revision and exit successfully.\n"
381 , RTPathFilename(argv[0]));
382 return RTEXITCODE_SUCCESS;
383
384 case 'V':
385 RTPrintf("$Revision: 82732 $\n");
386 return RTEXITCODE_SUCCESS;
387
388 default:
389 return RTGetOptPrintError(rc, &ValueUnion);
390 }
391 }
392
393 if (!strlen(g_FTPServerData.szRootDir))
394 {
395 /* By default use the current directory as serving root directory. */
396 rc = RTPathGetCurrent(g_FTPServerData.szRootDir, sizeof(g_FTPServerData.szRootDir));
397 if (RT_FAILURE(rc))
398 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Retrieving current directory failed: %Rrc", rc);
399 }
400
401 /* Initialize CWD. */
402 RTStrPrintf2(g_FTPServerData.szCWD, sizeof(g_FTPServerData.szCWD), "/");
403
404 /* Install signal handler. */
405 rc = signalHandlerInstall();
406 if (RT_SUCCESS(rc))
407 {
408 /*
409 * Create the FTP server instance.
410 */
411 RTFTPSERVERCALLBACKS Callbacks;
412 RT_ZERO(Callbacks);
413
414 Callbacks.pfnOnUserConnect = onUserConnect;
415 Callbacks.pfnOnUserAuthenticate = onUserAuthenticate;
416 Callbacks.pfnOnUserDisconnect = onUserDisonnect;
417 Callbacks.pfnOnFileOpen = onFileOpen;
418 Callbacks.pfnOnFileRead = onFileRead;
419 Callbacks.pfnOnFileClose = onFileClose;
420 Callbacks.pfnOnFileGetSize = onFileGetSize;
421 Callbacks.pfnOnFileStat = onFileStat;
422 Callbacks.pfnOnPathSetCurrent = onPathSetCurrent;
423 Callbacks.pfnOnPathGetCurrent = onPathGetCurrent;
424 Callbacks.pfnOnPathUp = onPathUp;
425 Callbacks.pfnOnList = onList;
426
427 RTFTPSERVER hFTPServer;
428 rc = RTFtpServerCreate(&hFTPServer, szAddress, uPort, &Callbacks,
429 &g_FTPServerData, sizeof(g_FTPServerData));
430 if (RT_SUCCESS(rc))
431 {
432 RTPrintf("Starting FTP server at %s:%RU16 ...\n", szAddress, uPort);
433 RTPrintf("Root directory is '%s'\n", g_FTPServerData.szRootDir);
434
435 RTPrintf("Running FTP server ...\n");
436
437 for (;;)
438 {
439 RTThreadSleep(200);
440
441 if (g_fCanceled)
442 break;
443 }
444
445 RTPrintf("Stopping FTP server ...\n");
446
447 int rc2 = RTFtpServerDestroy(hFTPServer);
448 if (RT_SUCCESS(rc))
449 rc = rc2;
450
451 RTPrintf("Stopped FTP server\n");
452 }
453 else
454 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFTPServerCreate failed: %Rrc", rc);
455
456 int rc2 = signalHandlerUninstall();
457 if (RT_SUCCESS(rc))
458 rc = rc2;
459 }
460
461 /* Set rcExit on failure in case we forgot to do so before. */
462 if (RT_FAILURE(rc))
463 rcExit = RTEXITCODE_FAILURE;
464
465 return rcExit;
466}
467
Note: See TracBrowser for help on using the repository browser.

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