VirtualBox

source: vbox/trunk/src/VBox/Runtime/generic/ftp-server.cpp@ 82773

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

IPRT/FTP: Shut up MSVC.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.5 KB
Line 
1/* $Id: ftp-server.cpp 82773 2020-01-15 16:51:55Z vboxsync $ */
2/** @file
3 * Generic FTP server (RFC 959) implementation.
4 * Partly also implements RFC 3659 (Extensions to FTP, for "SIZE", ++).
5 */
6
7/*
8 * Copyright (C) 2020 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * The contents of this file may alternatively be used under the terms
19 * of the Common Development and Distribution License Version 1.0
20 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
21 * VirtualBox OSE distribution, in which case the provisions of the
22 * CDDL are applicable instead of those of the GPL.
23 *
24 * You may elect to license modified versions of this file under the
25 * terms and conditions of either the GPL or the CDDL or both.
26 */
27
28/**
29 * Known limitations so far:
30 * - UTF-8 support only.
31 * - Only supports ASCII + binary (image type) file streams for now.
32 * - No support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++).
33 * - No FTPS / SFTP support.
34 * - No passive mode ("PASV") support.
35 * - No IPv6 support.
36 * - No proxy support.
37 * - No FXP support.
38 */
39
40
41/*********************************************************************************************************************************
42* Header Files *
43*********************************************************************************************************************************/
44#define LOG_GROUP RTLOGGROUP_FTP
45#include <iprt/ftp.h>
46#include "internal/iprt.h"
47#include "internal/magics.h"
48
49#include <iprt/asm.h>
50#include <iprt/assert.h>
51#include <iprt/err.h>
52#include <iprt/file.h> /* For file mode flags. */
53#include <iprt/getopt.h>
54#include <iprt/mem.h>
55#include <iprt/log.h>
56#include <iprt/path.h>
57#include <iprt/poll.h>
58#include <iprt/socket.h>
59#include <iprt/string.h>
60#include <iprt/system.h>
61#include <iprt/tcp.h>
62
63
64/*********************************************************************************************************************************
65* Structures and Typedefs *
66*********************************************************************************************************************************/
67/**
68 * Internal FTP server instance.
69 */
70typedef struct RTFTPSERVERINTERNAL
71{
72 /** Magic value. */
73 uint32_t u32Magic;
74 /** Callback table. */
75 RTFTPSERVERCALLBACKS Callbacks;
76 /** Pointer to TCP server instance. */
77 PRTTCPSERVER pTCPServer;
78 /** Number of currently connected clients. */
79 uint32_t cClients;
80 /** Pointer to user-specific data. Optional. */
81 void *pvUser;
82 /** Size of user-specific data. Optional. */
83 size_t cbUser;
84} RTFTPSERVERINTERNAL;
85/** Pointer to an internal FTP server instance. */
86typedef RTFTPSERVERINTERNAL *PRTFTPSERVERINTERNAL;
87
88
89/*********************************************************************************************************************************
90* Defined Constants And Macros *
91*********************************************************************************************************************************/
92/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
93#define RTFTPSERVER_VALID_RETURN_RC(hFTPServer, a_rc) \
94 do { \
95 AssertPtrReturn((hFTPServer), (a_rc)); \
96 AssertReturn((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC, (a_rc)); \
97 } while (0)
98
99/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
100#define RTFTPSERVER_VALID_RETURN(hFTPServer) RTFTPSERVER_VALID_RETURN_RC((hFTPServer), VERR_INVALID_HANDLE)
101
102/** Validates a handle and returns (void) if not valid. */
103#define RTFTPSERVER_VALID_RETURN_VOID(hFTPServer) \
104 do { \
105 AssertPtrReturnVoid(hFTPServer); \
106 AssertReturnVoid((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC); \
107 } while (0)
108
109/** Supported FTP server command IDs.
110 * Alphabetically, named after their official command names. */
111typedef enum RTFTPSERVER_CMD
112{
113 /** Invalid command, do not use. Always must come first. */
114 RTFTPSERVER_CMD_INVALID = 0,
115 /** Aborts the current command on the server. */
116 RTFTPSERVER_CMD_ABOR,
117 /** Changes the current working directory. */
118 RTFTPSERVER_CMD_CDUP,
119 /** Changes the current working directory. */
120 RTFTPSERVER_CMD_CWD,
121 /** Reports features supported by the server. */
122 RTFTPSERVER_CMD_FEAT,
123 /** Lists a directory. */
124 RTFTPSERVER_CMD_LIST,
125 /** Sets the transfer mode. */
126 RTFTPSERVER_CMD_MODE,
127 /** Sends a nop ("no operation") to the server. */
128 RTFTPSERVER_CMD_NOOP,
129 /** Sets the password for authentication. */
130 RTFTPSERVER_CMD_PASS,
131 /** Sets the port to use for the data connection. */
132 RTFTPSERVER_CMD_PORT,
133 /** Gets the current working directory. */
134 RTFTPSERVER_CMD_PWD,
135 /** Terminates the session (connection). */
136 RTFTPSERVER_CMD_QUIT,
137 /** Retrieves a specific file. */
138 RTFTPSERVER_CMD_RETR,
139 /** Retrieves the size of a file. */
140 RTFTPSERVER_CMD_SIZE,
141 /** Retrieves the current status of a transfer. */
142 RTFTPSERVER_CMD_STAT,
143 /** Sets the structure type to use. */
144 RTFTPSERVER_CMD_STRU,
145 /** Gets the server's OS info. */
146 RTFTPSERVER_CMD_SYST,
147 /** Sets the (data) representation type. */
148 RTFTPSERVER_CMD_TYPE,
149 /** Sets the user name for authentication. */
150 RTFTPSERVER_CMD_USER,
151 /** End marker. */
152 RTFTPSERVER_CMD_LAST,
153 /** The usual 32-bit hack. */
154 RTFTPSERVER_CMD_32BIT_HACK = 0x7fffffff
155} RTFTPSERVER_CMD;
156
157struct RTFTPSERVERCLIENT;
158
159/**
160 * Structure for maintaining a single data connection.
161 */
162typedef struct RTFTPSERVERDATACONN
163{
164 /** Pointer to associated client of this data connection. */
165 RTFTPSERVERCLIENT *pClient;
166 /** Data connection IP. */
167 RTNETADDRIPV4 Addr;
168 /** Data connection port number. */
169 uint16_t uPort;
170 /** The current data socket to use.
171 * Can be NIL_RTSOCKET if no data port has been specified (yet) or has been closed. */
172 RTSOCKET hSocket;
173 /** Thread serving the data connection. */
174 RTTHREAD hThread;
175 /** Thread started indicator. */
176 volatile bool fStarted;
177 /** Thread stop indicator. */
178 volatile bool fStop;
179 /** Thread stopped indicator. */
180 volatile bool fStopped;
181 /** Overall result when closing the data connection. */
182 int rc;
183 /** Number of command arguments. */
184 uint8_t cArgs;
185 /** Command arguments array. Optional and can be NULL.
186 * Will be free'd by the data connection thread. */
187 char** papszArgs;
188} RTFTPSERVERDATACONN;
189/** Pointer to a data connection struct. */
190typedef RTFTPSERVERDATACONN *PRTFTPSERVERDATACONN;
191
192/**
193 * Structure for maintaining an internal FTP server client.
194 */
195typedef struct RTFTPSERVERCLIENT
196{
197 /** Pointer to internal server state. */
198 PRTFTPSERVERINTERNAL pServer;
199 /** Socket handle the client is bound to. */
200 RTSOCKET hSocket;
201 /** Actual client state. */
202 RTFTPSERVERCLIENTSTATE State;
203 /** The last set data connection IP. */
204 RTNETADDRIPV4 DataConnAddr;
205 /** The last set data connection port number. */
206 uint16_t uDataConnPort;
207 /** Data connection information.
208 * At the moment we only allow one data connection per client at a time. */
209 PRTFTPSERVERDATACONN pDataConn;
210} RTFTPSERVERCLIENT;
211/** Pointer to an internal FTP server client state. */
212typedef RTFTPSERVERCLIENT *PRTFTPSERVERCLIENT;
213
214/** Function pointer declaration for a specific FTP server command handler. */
215typedef DECLCALLBACK(int) FNRTFTPSERVERCMD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs);
216/** Pointer to a FNRTFTPSERVERCMD(). */
217typedef FNRTFTPSERVERCMD *PFNRTFTPSERVERCMD;
218
219/** Handles a FTP server callback with no arguments and returns. */
220#define RTFTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
221 do \
222 { \
223 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
224 if (pCallbacks->a_Name) \
225 { \
226 RTFTPCALLBACKDATA Data = { &pClient->State }; \
227 return pCallbacks->a_Name(&Data); \
228 } \
229 else \
230 return VERR_NOT_IMPLEMENTED; \
231 } while (0)
232
233/** Handles a FTP server callback with no arguments and sets rc accordingly. */
234#define RTFTPSERVER_HANDLE_CALLBACK(a_Name) \
235 do \
236 { \
237 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
238 if (pCallbacks->a_Name) \
239 { \
240 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
241 rc = pCallbacks->a_Name(&Data); \
242 } \
243 else \
244 rc = VERR_NOT_IMPLEMENTED; \
245 } while (0)
246
247/** Handles a FTP server callback with arguments and sets rc accordingly. */
248#define RTFTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
249 do \
250 { \
251 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
252 if (pCallbacks->a_Name) \
253 { \
254 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
255 rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
256 } \
257 else \
258 rc = VERR_NOT_IMPLEMENTED; \
259 } while (0)
260
261/** Handles a FTP server callback with arguments and returns. */
262#define RTFTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
263 do \
264 { \
265 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
266 if (pCallbacks->a_Name) \
267 { \
268 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
269 return pCallbacks->a_Name(&Data, __VA_ARGS__); \
270 } \
271 else \
272 return VERR_NOT_IMPLEMENTED; \
273 } while (0)
274
275
276/*********************************************************************************************************************************
277* Defined Constants And Macros *
278*********************************************************************************************************************************/
279
280static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort);
281static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn);
282static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn);
283static int rtFtpServerDataConnStart(PRTFTPSERVERDATACONN pDataConn, PFNRTTHREAD pfnThread, uint8_t cArgs, const char * const *apcszArgs);
284static int rtFtpServerDataConnStop(PRTFTPSERVERDATACONN pDataConn);
285static void rtFtpServerDataConnDestroy(PRTFTPSERVERDATACONN pDataConn);
286
287static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState);
288
289/**
290 * Function prototypes for command handlers.
291 */
292static FNRTFTPSERVERCMD rtFtpServerHandleABOR;
293static FNRTFTPSERVERCMD rtFtpServerHandleCDUP;
294static FNRTFTPSERVERCMD rtFtpServerHandleCWD;
295static FNRTFTPSERVERCMD rtFtpServerHandleFEAT;
296static FNRTFTPSERVERCMD rtFtpServerHandleLIST;
297static FNRTFTPSERVERCMD rtFtpServerHandleMODE;
298static FNRTFTPSERVERCMD rtFtpServerHandleNOOP;
299static FNRTFTPSERVERCMD rtFtpServerHandlePASS;
300static FNRTFTPSERVERCMD rtFtpServerHandlePORT;
301static FNRTFTPSERVERCMD rtFtpServerHandlePWD;
302static FNRTFTPSERVERCMD rtFtpServerHandleQUIT;
303static FNRTFTPSERVERCMD rtFtpServerHandleRETR;
304static FNRTFTPSERVERCMD rtFtpServerHandleSIZE;
305static FNRTFTPSERVERCMD rtFtpServerHandleSTAT;
306static FNRTFTPSERVERCMD rtFtpServerHandleSTRU;
307static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
308static FNRTFTPSERVERCMD rtFtpServerHandleTYPE;
309static FNRTFTPSERVERCMD rtFtpServerHandleUSER;
310
311/**
312 * Structure for maintaining a single command entry for the command table.
313 */
314typedef struct RTFTPSERVER_CMD_ENTRY
315{
316 /** Command ID. */
317 RTFTPSERVER_CMD enmCmd;
318 /** Command represented as ASCII string. */
319 char szCmd[RTFTPSERVER_MAX_CMD_LEN];
320 /** Whether the commands needs a logged in (valid) user. */
321 bool fNeedsUser;
322 /** Function pointer invoked to handle the command. */
323 PFNRTFTPSERVERCMD pfnCmd;
324} RTFTPSERVER_CMD_ENTRY;
325/** Pointer to a command entry. */
326typedef RTFTPSERVER_CMD_ENTRY *PRTFTPSERVER_CMD_ENTRY;
327
328/**
329 * Table of handled commands.
330 */
331const RTFTPSERVER_CMD_ENTRY g_aCmdMap[] =
332{
333 { RTFTPSERVER_CMD_ABOR, "ABOR", true, rtFtpServerHandleABOR },
334 { RTFTPSERVER_CMD_CDUP, "CDUP", true, rtFtpServerHandleCDUP },
335 { RTFTPSERVER_CMD_CWD, "CWD", true, rtFtpServerHandleCWD },
336 { RTFTPSERVER_CMD_FEAT, "FEAT", true, rtFtpServerHandleFEAT },
337 { RTFTPSERVER_CMD_LIST, "LIST", true, rtFtpServerHandleLIST },
338 { RTFTPSERVER_CMD_MODE, "MODE", true, rtFtpServerHandleMODE },
339 { RTFTPSERVER_CMD_NOOP, "NOOP", true, rtFtpServerHandleNOOP },
340 { RTFTPSERVER_CMD_PASS, "PASS", false, rtFtpServerHandlePASS },
341 { RTFTPSERVER_CMD_PORT, "PORT", true, rtFtpServerHandlePORT },
342 { RTFTPSERVER_CMD_PWD, "PWD", true, rtFtpServerHandlePWD },
343 { RTFTPSERVER_CMD_QUIT, "QUIT", false, rtFtpServerHandleQUIT },
344 { RTFTPSERVER_CMD_RETR, "RETR", true, rtFtpServerHandleRETR },
345 { RTFTPSERVER_CMD_SIZE, "SIZE", true, rtFtpServerHandleSIZE },
346 { RTFTPSERVER_CMD_STAT, "STAT", true, rtFtpServerHandleSTAT },
347 { RTFTPSERVER_CMD_STRU, "STRU", true, rtFtpServerHandleSTRU },
348 { RTFTPSERVER_CMD_SYST, "SYST", false, rtFtpServerHandleSYST },
349 { RTFTPSERVER_CMD_TYPE, "TYPE", true, rtFtpServerHandleTYPE },
350 { RTFTPSERVER_CMD_USER, "USER", false, rtFtpServerHandleUSER },
351 { RTFTPSERVER_CMD_LAST, "", false, NULL }
352};
353
354/** Feature string which represents all commands we support in addition to RFC 959.
355 * Must match the command table above.
356 *
357 * Don't forget the terminating ";" at each feature. */
358#define RTFTPSERVER_FEATURES_STRING \
359 "SIZE;" /* Supports reporting file sizes. */
360
361
362/*********************************************************************************************************************************
363* Protocol Functions *
364*********************************************************************************************************************************/
365
366/**
367 * Replies a (three digit) reply code back to the client.
368 *
369 * @returns VBox status code.
370 * @param pClient Client to reply to.
371 * @param enmReply Reply code to send.
372 */
373static int rtFtpServerSendReplyRc(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply)
374{
375 /* Note: If we don't supply any additional text, make sure to include an empty stub, as
376 * some clients expect this as part of their parsing code. */
377 char szReply[32];
378 RTStrPrintf2(szReply, sizeof(szReply), "%RU32 -\r\n", enmReply);
379
380 LogFlowFunc(("Sending reply code %RU32\n", enmReply));
381
382 return RTTcpWrite(pClient->hSocket, szReply, strlen(szReply) + 1);
383}
384
385/**
386 * Replies a (three digit) reply code with a custom message back to the client.
387 *
388 * @returns VBox status code.
389 * @param pClient Client to reply to.
390 * @param enmReply Reply code to send.
391 * @param pcszFormat Format string of message to send with the reply code.
392 */
393static int rtFtpServerSendReplyRcEx(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply,
394 const char *pcszFormat, ...)
395{
396 char *pszMsg = NULL;
397
398 va_list args;
399 va_start(args, pcszFormat);
400 char *pszFmt = NULL;
401 const int cch = RTStrAPrintfV(&pszFmt, pcszFormat, args);
402 va_end(args);
403 AssertReturn(cch > 0, VERR_NO_MEMORY);
404
405 int rc = RTStrAPrintf(&pszMsg, "%RU32 -", enmReply);
406 AssertRCReturn(rc, rc);
407
408 /** @todo Support multi-line replies (see 4.2ff). */
409
410 if (pszFmt)
411 {
412 rc = RTStrAAppend(&pszMsg, " ");
413 AssertRCReturn(rc, rc);
414
415 rc = RTStrAAppend(&pszMsg, pszFmt);
416 AssertRCReturn(rc, rc);
417 }
418
419
420 rc = RTStrAAppend(&pszMsg, "\r\n");
421 AssertRCReturn(rc, rc);
422
423 RTStrFree(pszFmt);
424
425 rc = RTTcpWrite(pClient->hSocket, pszMsg, strlen(pszMsg) + 1 /* Include termination */);
426
427 RTStrFree(pszMsg);
428
429 return rc;
430}
431
432/**
433 * Replies a string back to the client.
434 *
435 * @returns VBox status code.
436 * @param pClient Client to reply to.
437 * @param pcszFormat Format to reply.
438 * @param ... Format arguments.
439 */
440static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszFormat, ...)
441{
442 va_list args;
443 va_start(args, pcszFormat);
444 char *psz = NULL;
445 const int cch = RTStrAPrintfV(&psz, pcszFormat, args);
446 va_end(args);
447 AssertReturn(cch > 0, VERR_NO_MEMORY);
448
449 int rc = RTStrAAppend(&psz, "\r\n");
450 AssertRCReturn(rc, rc);
451
452 rc = RTTcpWrite(pClient->hSocket, psz, strlen(psz) + 1 /* Include termination */);
453
454 RTStrFree(psz);
455
456 return rc;
457}
458
459/**
460 * Looks up an user account.
461 *
462 * @returns VBox status code, or VERR_NOT_FOUND if user has not been found.
463 * @param pClient Client to look up user for.
464 * @param pcszUser User name to look up.
465 */
466static int rtFtpServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pcszUser)
467{
468 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pcszUser);
469}
470
471/**
472 * Handles the actual client authentication.
473 *
474 * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed.
475 * @param pClient Client to authenticate.
476 * @param pcszUser User name to authenticate with.
477 * @param pcszPassword Password to authenticate with.
478 */
479static int rtFtpServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pcszUser, const char *pcszPassword)
480{
481 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pcszUser, pcszPassword);
482}
483
484/**
485 * Converts a RTFSOBJINFO struct to a string.
486 *
487 * @returns VBox status code.
488 * @param pObjInfo RTFSOBJINFO object to convert.
489 * @param pszFsObjInfo Where to store the output string.
490 * @param cbFsObjInfo Size of the output string in bytes.
491 */
492static int rtFtpServerFsObjInfoToStr(PRTFSOBJINFO pObjInfo, char *pszFsObjInfo, size_t cbFsObjInfo)
493{
494 RTFMODE fMode = pObjInfo->Attr.fMode;
495 char chFileType;
496 switch (fMode & RTFS_TYPE_MASK)
497 {
498 case RTFS_TYPE_FIFO: chFileType = 'f'; break;
499 case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
500 case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
501 case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
502 case RTFS_TYPE_FILE: chFileType = '-'; break;
503 case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
504 case RTFS_TYPE_SOCKET: chFileType = 's'; break;
505 case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
506 default: chFileType = '?'; break;
507 }
508
509 char szTimeBirth[RTTIME_STR_LEN];
510 char szTimeChange[RTTIME_STR_LEN];
511 char szTimeModification[RTTIME_STR_LEN];
512 char szTimeAccess[RTTIME_STR_LEN];
513
514#define INFO_TO_STR(a_Format, ...) \
515 do \
516 { \
517 const ssize_t cchSize = RTStrPrintf2(szTemp, sizeof(szTemp), a_Format, __VA_ARGS__); \
518 AssertReturn(cchSize > 0, VERR_BUFFER_OVERFLOW); \
519 const int rc2 = RTStrCat(pszFsObjInfo, cbFsObjInfo, szTemp); \
520 AssertRCReturn(rc2, rc2); \
521 } while (0);
522
523 char szTemp[128];
524
525 INFO_TO_STR("%c", chFileType);
526 INFO_TO_STR("%c%c%c",
527 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
528 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
529 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
530 INFO_TO_STR("%c%c%c",
531 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
532 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
533 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
534 INFO_TO_STR("%c%c%c",
535 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
536 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
537 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
538
539 INFO_TO_STR( " %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
540 fMode & RTFS_DOS_READONLY ? 'R' : '-',
541 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
542 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
543 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
544 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
545 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
546 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
547 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
548 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
549 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
550 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
551 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
552 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
553 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
554
555 INFO_TO_STR( " %d %4d %4d %10lld %10lld",
556 pObjInfo->Attr.u.Unix.cHardlinks,
557 pObjInfo->Attr.u.Unix.uid,
558 pObjInfo->Attr.u.Unix.gid,
559 pObjInfo->cbObject,
560 pObjInfo->cbAllocated);
561
562 INFO_TO_STR( " %s %s %s %s",
563 RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
564 RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
565 RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
566 RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
567
568#undef INFO_TO_STR
569
570 return VINF_SUCCESS;
571}
572
573/**
574 * Parses a string which consists of an IPv4 (ww,xx,yy,zz) and a port number (hi,lo), all separated by comma delimiters.
575 * See RFC 959, 4.1.2.
576 *
577 * @returns VBox status code.
578 * @param pcszStr String to parse.
579 * @param pAddr Where to store the IPv4 address on success.
580 * @param puPort Where to store the port number on success.
581 */
582static int rtFtpParseHostAndPort(const char *pcszStr, PRTNETADDRIPV4 pAddr, uint16_t *puPort)
583{
584 AssertPtrReturn(pcszStr, VERR_INVALID_POINTER);
585 AssertPtrReturn(pAddr, VERR_INVALID_POINTER);
586 AssertPtrReturn(puPort, VERR_INVALID_POINTER);
587
588 char *pszNext;
589 int rc;
590
591 /* Parse IP (v4). */
592 /** @todo I don't think IPv6 ever will be a thing here, or will it? */
593 rc = RTStrToUInt8Ex(pcszStr, &pszNext, 10, &pAddr->au8[0]);
594 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
595 return VERR_INVALID_PARAMETER;
596 if (*pszNext++ != ',')
597 return VERR_INVALID_PARAMETER;
598
599 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]);
600 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
601 return VERR_INVALID_PARAMETER;
602 if (*pszNext++ != ',')
603 return VERR_INVALID_PARAMETER;
604
605 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]);
606 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
607 return VERR_INVALID_PARAMETER;
608 if (*pszNext++ != ',')
609 return VERR_INVALID_PARAMETER;
610
611 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]);
612 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
613 return VERR_INVALID_PARAMETER;
614 if (*pszNext++ != ',')
615 return VERR_INVALID_PARAMETER;
616
617 /* Parse port. */
618 uint8_t uPortHi;
619 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortHi);
620 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
621 return VERR_INVALID_PARAMETER;
622 if (*pszNext++ != ',')
623 return VERR_INVALID_PARAMETER;
624 uint8_t uPortLo;
625 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortLo);
626 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
627 return VERR_INVALID_PARAMETER;
628
629 *puPort = RT_MAKE_U16(uPortLo, uPortHi);
630
631 return rc;
632}
633
634/**
635 * Duplicates a command argument vector.
636 *
637 * @returns Duplicated argument vector or NULL if failed or no arguments given. Needs to be free'd with rtFtpCmdArgsFree().
638 * @param cArgs Number of arguments in argument vector.
639 * @param apcszArgs Pointer to argument vector to duplicate.
640 */
641static char** rtFtpCmdArgsDup(uint8_t cArgs, const char * const *apcszArgs)
642{
643 if (!cArgs)
644 return NULL;
645
646 char **apcszArgsDup = (char **)RTMemAlloc(cArgs * sizeof(char *));
647 if (!apcszArgsDup)
648 {
649 AssertFailed();
650 return NULL;
651 }
652
653 int rc2 = VINF_SUCCESS;
654
655 uint8_t i;
656 for (i = 0; i < cArgs; i++)
657 {
658 apcszArgsDup[i] = RTStrDup(apcszArgs[i]);
659 if (!apcszArgsDup[i])
660 rc2 = VERR_NO_MEMORY;
661 }
662
663 if (RT_FAILURE(rc2))
664 {
665 while (i--)
666 RTStrFree(apcszArgsDup[i]);
667
668 RTMemFree(apcszArgsDup);
669 return NULL;
670 }
671
672 return apcszArgsDup;
673}
674
675/**
676 * Frees a command argument vector.
677 *
678 * @param cArgs Number of arguments in argument vector.
679 * @param papcszArgs Pointer to argument vector to free.
680 */
681static void rtFtpCmdArgsFree(uint8_t cArgs, char **papcszArgs)
682{
683 while (cArgs--)
684 RTStrFree(papcszArgs[cArgs]);
685
686 RTMemFree(papcszArgs);
687}
688
689/**
690 * Opens a data connection to the client.
691 *
692 * @returns VBox status code.
693 * @param pDataConn Data connection to open.
694 * @param pAddr Address for the data connection.
695 * @param uPort Port for the data connection.
696 */
697static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort)
698{
699 LogFlowFuncEnter();
700
701 /** @todo Implement IPv6 handling here. */
702 char szAddress[32];
703 const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8",
704 pAddr->au8[0], pAddr->au8[1], pAddr->au8[2], pAddr->au8[3]);
705 AssertReturn(cchAdddress > 0, VERR_NO_MEMORY);
706
707 int rc = VINF_SUCCESS; /* Shut up MSVC. */
708
709 /* Try a bit harder if the data connection is not ready (yet). */
710 for (int i = 0; i < 10; i++)
711 {
712 rc = RTTcpClientConnect(szAddress, uPort, &pDataConn->hSocket);
713 if (RT_SUCCESS(rc))
714 break;
715 RTThreadSleep(100);
716 }
717
718 LogFlowFuncLeaveRC(rc);
719 return rc;
720}
721
722/**
723 * Closes a data connection to the client.
724 *
725 * @returns VBox status code.
726 * @param pDataConn Data connection to close.
727 */
728static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn)
729{
730 int rc = VINF_SUCCESS;
731
732 if (pDataConn->hSocket != NIL_RTSOCKET)
733 {
734 LogFlowFuncEnter();
735
736 rc = RTTcpClientClose(pDataConn->hSocket);
737 pDataConn->hSocket = NIL_RTSOCKET;
738 }
739
740 LogFlowFuncLeaveRC(rc);
741 return rc;
742}
743
744/**
745 * Writes data to the data connection.
746 *
747 * @returns VBox status code.
748 * @param pDataConn Data connection to write to.
749 * @param pvData Data to write.
750 * @param cbData Size (in bytes) of data to write.
751 * @param pcbWritten How many bytes were written. Optional and unused atm.
752 */
753static int rtFtpServerDataConnWrite(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData, size_t *pcbWritten)
754{
755 RT_NOREF(pcbWritten);
756
757 return RTTcpWrite(pDataConn->hSocket, pvData, cbData);
758}
759
760/**
761 * Data connection thread for writing (sending) a file to the client.
762 *
763 * @returns VBox status code.
764 * @param ThreadSelf Thread handle. Unused at the moment.
765 * @param pvUser Pointer to user-provided data. Of type PRTFTPSERVERCLIENT.
766 */
767static DECLCALLBACK(int) rtFtpServerDataConnFileWriteThread(RTTHREAD ThreadSelf, void *pvUser)
768{
769 RT_NOREF(ThreadSelf);
770
771 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
772 AssertPtr(pClient);
773
774 PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn;
775 AssertPtr(pDataConn);
776
777 LogFlowFuncEnter();
778
779 uint32_t cbBuf = _64K; /** @todo Improve this. */
780 void *pvBuf = RTMemAlloc(cbBuf);
781 if (!pvBuf)
782 return VERR_NO_MEMORY;
783
784 int rc;
785
786 /* Set start indicator. */
787 pDataConn->fStarted = true;
788
789 RTThreadUserSignal(RTThreadSelf());
790
791 AssertPtr(pDataConn->papszArgs);
792 const char *pcszFile = pDataConn->papszArgs[0];
793 AssertPtr(pcszFile);
794
795 void *pvHandle = NULL; /* Opaque handle known to the actual implementation. */
796
797 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileOpen, pcszFile,
798 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pvHandle);
799 if (RT_SUCCESS(rc))
800 {
801 LogFlowFunc(("Transfer started\n"));
802
803 do
804 {
805 size_t cbRead = 0;
806 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead);
807 if ( RT_SUCCESS(rc)
808 && cbRead)
809 {
810 rc = rtFtpServerDataConnWrite(pDataConn, pvBuf, cbRead, NULL /* pcbWritten */);
811 }
812
813 if ( !cbRead
814 || ASMAtomicReadBool(&pDataConn->fStop))
815 {
816 break;
817 }
818 }
819 while (RT_SUCCESS(rc));
820
821 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle);
822
823 LogFlowFunc(("Transfer done\n"));
824 }
825
826 RTMemFree(pvBuf);
827 pvBuf = NULL;
828
829 pDataConn->fStopped = true;
830 pDataConn->rc = rc;
831
832 LogFlowFuncLeaveRC(rc);
833 return rc;
834}
835
836/**
837 * Creates a data connection.
838 *
839 * @returns VBox status code.
840 * @param pClient Client to create data connection for.
841 * @param ppDataConn Where to return the (allocated) data connection.
842 */
843static int rtFtpServerDataConnCreate(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN *ppDataConn)
844{
845 if (pClient->pDataConn)
846 return VERR_FTP_DATA_CONN_LIMIT_REACHED;
847
848 PRTFTPSERVERDATACONN pDataConn = (PRTFTPSERVERDATACONN)RTMemAllocZ(sizeof(RTFTPSERVERDATACONN));
849 if (!pDataConn)
850 return VERR_NO_MEMORY;
851
852 rtFtpServerDataConnReset(pDataConn);
853
854 pDataConn->pClient = pClient;
855
856 /* Use the last configured addr + port. */
857 pDataConn->Addr = pClient->DataConnAddr;
858 pDataConn->uPort = pClient->uDataConnPort;
859
860 *ppDataConn = pDataConn;
861
862 LogFlowFuncLeaveRC(VINF_SUCCESS);
863 return VINF_SUCCESS;
864}
865
866/**
867 * Starts a data connection.
868 *
869 * @returns VBox status code.
870 * @param pDataConn Data connection to start.
871 * @param pfnThread Thread function for the data connection to use.
872 * @param cArgs Number of arguments.
873 * @param apcszArgs Array of arguments.
874 */
875static int rtFtpServerDataConnStart(PRTFTPSERVERDATACONN pDataConn, PFNRTTHREAD pfnThread,
876 uint8_t cArgs, const char * const *apcszArgs)
877{
878 AssertPtrReturn(pDataConn, VERR_INVALID_POINTER);
879 AssertPtrReturn(pfnThread, VERR_INVALID_POINTER);
880
881 AssertReturn(!pDataConn->fStarted, VERR_WRONG_ORDER);
882 AssertReturn(!pDataConn->fStop, VERR_WRONG_ORDER);
883 AssertReturn(!pDataConn->fStopped, VERR_WRONG_ORDER);
884
885 int rc = VINF_SUCCESS;
886
887 if (cArgs)
888 {
889 pDataConn->papszArgs = rtFtpCmdArgsDup(cArgs, apcszArgs);
890 if (!pDataConn->papszArgs)
891 rc = VERR_NO_MEMORY;
892 }
893
894 if (RT_SUCCESS(rc))
895 {
896 pDataConn->cArgs = cArgs;
897
898 rc = rtFtpServerDataConnOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort);
899 if (RT_SUCCESS(rc))
900 {
901 rc = RTThreadCreate(&pDataConn->hThread, pfnThread,
902 pDataConn->pClient, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
903 "ftpdata");
904 if (RT_SUCCESS(rc))
905 {
906 int rc2 = RTThreadUserWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */);
907 AssertRC(rc2);
908
909 if (!pDataConn->fStarted) /* Did the thread indicate that it started correctly? */
910 rc = VERR_FTP_DATA_CONN_INIT_FAILED;
911 }
912
913 if (RT_FAILURE(rc))
914 rtFtpServerDataConnClose(pDataConn);
915 }
916 }
917
918 if (RT_FAILURE(rc))
919 {
920 rtFtpCmdArgsFree(pDataConn->cArgs, pDataConn->papszArgs);
921
922 pDataConn->cArgs = 0;
923 pDataConn->papszArgs = NULL;
924 }
925
926 LogFlowFuncLeaveRC(rc);
927 return rc;
928}
929
930/**
931 * Stops a data connection.
932 *
933 * @returns VBox status code.
934 * @param pDataConn Data connection to stop.
935 */
936static int rtFtpServerDataConnStop(PRTFTPSERVERDATACONN pDataConn)
937{
938 if (!pDataConn)
939 return VINF_SUCCESS;
940
941 LogFlowFuncEnter();
942
943 int rc = VINF_SUCCESS;
944
945 if (pDataConn->hThread != NIL_RTTHREAD)
946 {
947 /* Set stop indicator. */
948 pDataConn->fStop = true;
949
950 int rcThread = VERR_WRONG_ORDER;
951 rc = RTThreadWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */, &rcThread);
952 }
953
954 if (RT_SUCCESS(rc))
955 rtFtpServerDataConnClose(pDataConn);
956
957 LogFlowFuncLeaveRC(rc);
958 return rc;
959}
960
961/**
962 * Destroys a data connection.
963 *
964 * @returns VBox status code.
965 * @param pDataConn Data connection to destroy. The pointer is not valid anymore after successful return.
966 */
967static void rtFtpServerDataConnDestroy(PRTFTPSERVERDATACONN pDataConn)
968{
969 if (!pDataConn)
970 return;
971
972 LogFlowFuncEnter();
973
974 rtFtpServerDataConnClose(pDataConn);
975 rtFtpCmdArgsFree(pDataConn->cArgs, pDataConn->papszArgs);
976
977 RTMemFree(pDataConn);
978 pDataConn = NULL;
979
980 LogFlowFuncLeave();
981 return;
982}
983
984/**
985 * Resets a data connection structure.
986 *
987 * @returns VBox status code.
988 * @param pDataConn Data connection structure to reset.
989 */
990static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn)
991{
992 LogFlowFuncEnter();
993
994 pDataConn->hSocket = NIL_RTSOCKET;
995 pDataConn->uPort = 20; /* Default port to use. */
996 pDataConn->hThread = NIL_RTTHREAD;
997 pDataConn->fStarted = false;
998 pDataConn->fStop = false;
999 pDataConn->fStopped = false;
1000 pDataConn->rc = VERR_IPE_UNINITIALIZED_STATUS;
1001}
1002
1003
1004/*********************************************************************************************************************************
1005* Command Protocol Handlers *
1006*********************************************************************************************************************************/
1007
1008static int rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1009{
1010 RT_NOREF(cArgs, apcszArgs);
1011
1012 int rc = rtFtpServerDataConnClose(pClient->pDataConn);
1013 if (RT_SUCCESS(rc))
1014 {
1015 rtFtpServerDataConnDestroy(pClient->pDataConn);
1016 pClient->pDataConn = NULL;
1017
1018 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1019 }
1020
1021 return rc;
1022}
1023
1024static int rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1025{
1026 RT_NOREF(cArgs, apcszArgs);
1027
1028 int rc;
1029
1030 RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp);
1031
1032 if (RT_SUCCESS(rc))
1033 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1034
1035 return rc;
1036}
1037
1038static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1039{
1040 if (cArgs != 1)
1041 return VERR_INVALID_PARAMETER;
1042
1043 int rc;
1044
1045 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, apcszArgs[0]);
1046
1047 if (RT_SUCCESS(rc))
1048 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1049
1050 return rc;
1051}
1052
1053static int rtFtpServerHandleFEAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1054{
1055 RT_NOREF(cArgs, apcszArgs);
1056
1057 int rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_SYSTEM_STATUS); /* Features begin */
1058 if (RT_SUCCESS(rc))
1059 {
1060 rc = rtFtpServerSendReplyStr(pClient, RTFTPSERVER_FEATURES_STRING);
1061 if (RT_SUCCESS(rc))
1062 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_SYSTEM_STATUS); /* Features end */
1063 }
1064
1065 return rc;
1066}
1067
1068/**
1069 * Thread for handling the LIST command's output in a separate data connection.
1070 *
1071 * @returns VBox status code.
1072 * @param ThreadSelf Thread handle. Unused.
1073 * @param pvUser User-provided arguments. Of type PRTFTPSERVERCLIENT.
1074 */
1075static DECLCALLBACK(int) rtFtpServerDataConnListThread(RTTHREAD ThreadSelf, void *pvUser)
1076{
1077 RT_NOREF(ThreadSelf);
1078
1079 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
1080 AssertPtr(pClient);
1081
1082 PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn;
1083 AssertPtr(pDataConn);
1084
1085 LogFlowFuncEnter();
1086
1087 uint32_t cbBuf = _64K; /** @todo Improve this. */
1088 void *pvBuf = RTMemAlloc(cbBuf);
1089 if (!pvBuf)
1090 return VERR_NO_MEMORY;
1091
1092 int rc;
1093
1094 /* Set start indicator. */
1095 pDataConn->fStarted = true;
1096
1097 RTThreadUserSignal(RTThreadSelf());
1098
1099 for (;;)
1100 {
1101 /* The first argument might indicate a directory to list.
1102 * If no argument is given, the implementation must use the last directory set. */
1103 size_t cbRead = 0;
1104 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnList,
1105 pDataConn->cArgs == 1
1106 ? pDataConn->papszArgs[0] : NULL, pvBuf, cbBuf, &cbRead);
1107 if (RT_SUCCESS(rc))
1108 {
1109 int rc2 = rtFtpServerDataConnWrite(pDataConn, pvBuf, cbRead, NULL /* pcbWritten */);
1110 AssertRC(rc2);
1111
1112 if (rc == VINF_EOF)
1113 break;
1114 }
1115 else
1116 break;
1117
1118 if (ASMAtomicReadBool(&pDataConn->fStop))
1119 break;
1120 }
1121
1122 RTMemFree(pvBuf);
1123 pvBuf = NULL;
1124
1125 pDataConn->fStopped = true;
1126 pDataConn->rc = rc;
1127
1128 LogFlowFuncLeaveRC(rc);
1129 return rc;
1130}
1131
1132static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1133{
1134 int rc;
1135
1136 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, cArgs ? apcszArgs[0] : NULL, NULL /* PRTFSOBJINFO */);
1137
1138 RTFTPSERVER_REPLY rcClient = RTFTPSERVER_REPLY_INVALID;
1139
1140 if (RT_SUCCESS(rc))
1141 {
1142 int rc2 = rtFtpServerSendReplyRc(pClient, pClient->pDataConn
1143 ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN
1144 : RTFTPSERVER_REPLY_FILE_STS_OK_OPENING_DATA_CONN);
1145 if (RT_SUCCESS(rc))
1146 rc = rc2;
1147
1148 if (RT_SUCCESS(rc))
1149 {
1150 rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn);
1151 if (RT_SUCCESS(rc))
1152 {
1153 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnListThread, cArgs, apcszArgs);
1154 }
1155
1156 rc2 = rtFtpServerSendReplyRc(pClient, RT_SUCCESS(rc)
1157 ? RTFTPSERVER_REPLY_FILE_ACTION_OKAY_COMPLETED
1158 : RTFTPSERVER_REPLY_CLOSING_DATA_CONN);
1159 if (RT_SUCCESS(rc))
1160 rc = rc2;
1161 }
1162 }
1163 else
1164 rcClient = RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN;
1165
1166 return rc;
1167}
1168
1169static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1170{
1171 RT_NOREF(pClient, cArgs, apcszArgs);
1172
1173 /** @todo Anything to do here? */
1174 return VINF_SUCCESS;
1175}
1176
1177static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1178{
1179 RT_NOREF(cArgs, apcszArgs);
1180
1181 /* Save timestamp of last command sent. */
1182 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1183
1184 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1185}
1186
1187static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1188{
1189 if (cArgs != 1)
1190 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1191
1192 const char *pcszPassword = apcszArgs[0];
1193 AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
1194
1195 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
1196 if (RT_SUCCESS(rc))
1197 {
1198 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
1199 }
1200 else
1201 {
1202 pClient->State.cFailedLoginAttempts++;
1203
1204 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1205 if (RT_SUCCESS(rc))
1206 rc = rc2;
1207 }
1208
1209 return rc;
1210}
1211
1212static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1213{
1214 if (cArgs != 1)
1215 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1216
1217 RTFTPSERVER_REPLY rcClient;
1218
1219 int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConnAddr, &pClient->uDataConnPort);
1220 if (RT_SUCCESS(rc))
1221 rcClient = RTFTPSERVER_REPLY_OKAY;
1222 else
1223 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
1224
1225 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
1226 if (RT_SUCCESS(rc))
1227 rc = rc2;
1228
1229 return rc;
1230}
1231
1232static int rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1233{
1234 RT_NOREF(cArgs, apcszArgs);
1235
1236 int rc;
1237
1238 char szPWD[RTPATH_MAX];
1239
1240 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
1241
1242 if (RT_SUCCESS(rc))
1243 rc = rtFtpServerSendReplyRcEx(pClient, RTFTPSERVER_REPLY_PATHNAME_OK, "\"%s\"", szPWD); /* See RFC 959, APPENDIX II. */
1244
1245 return rc;
1246}
1247
1248static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1249{
1250 RT_NOREF(cArgs, apcszArgs);
1251
1252 rtFtpServerClientStateReset(&pClient->State);
1253
1254 int rc = rtFtpServerDataConnClose(pClient->pDataConn);
1255 if (RT_SUCCESS(rc))
1256 {
1257 rtFtpServerDataConnDestroy(pClient->pDataConn);
1258 pClient->pDataConn = NULL;
1259 }
1260
1261 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1262 if (RT_SUCCESS(rc))
1263 rc = rc2;
1264
1265 return rc;
1266}
1267
1268static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1269{
1270 if (cArgs != 1) /* File name needs to be present. */
1271 return VERR_INVALID_PARAMETER;
1272
1273 int rc;
1274
1275 const char *pcszPath = apcszArgs[0];
1276
1277 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
1278
1279 if (RT_SUCCESS(rc))
1280 {
1281 if (pClient->pDataConn)
1282 {
1283 /* Note: Data connection gets created when the PORT command was sent. */
1284 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnFileWriteThread, cArgs, apcszArgs);
1285 if (RT_SUCCESS(rc))
1286 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_FILE_STS_OK_OPENING_DATA_CONN);
1287 }
1288 else
1289 rc = VERR_FTP_DATA_CONN_NOT_FOUND;
1290 }
1291
1292 if (RT_FAILURE(rc))
1293 {
1294 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1295 AssertRC(rc2);
1296 }
1297
1298 return rc;
1299}
1300
1301static int rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1302{
1303 if (cArgs != 1)
1304 return VERR_INVALID_PARAMETER;
1305
1306 int rc;
1307
1308 const char *pcszPath = apcszArgs[0];
1309 uint64_t uSize = 0;
1310
1311 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pcszPath, &uSize);
1312
1313 if (RT_SUCCESS(rc))
1314 {
1315 rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
1316 }
1317 else
1318 {
1319 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1320 AssertRC(rc2);
1321 }
1322
1323 return rc;
1324}
1325
1326static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1327{
1328 if (cArgs != 1)
1329 return VERR_INVALID_PARAMETER;
1330
1331 int rc;
1332
1333 RTFSOBJINFO objInfo;
1334 RT_ZERO(objInfo);
1335
1336 const char *pcszPath = apcszArgs[0];
1337
1338 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, &objInfo);
1339
1340 if (RT_SUCCESS(rc))
1341 {
1342 char szFsObjInfo[_4K]; /** @todo Check this size. */
1343 rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
1344 if (RT_SUCCESS(rc))
1345 {
1346 char szFsPathInfo[RTPATH_MAX + 16];
1347 const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pcszPath), pcszPath);
1348 if (cchPathInfo > 0)
1349 {
1350 rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
1351 if (RT_SUCCESS(rc))
1352 rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
1353 }
1354 else
1355 rc = VERR_BUFFER_OVERFLOW;
1356 }
1357 }
1358
1359 if (RT_FAILURE(rc))
1360 {
1361 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1362 AssertRC(rc2);
1363 }
1364
1365 return rc;
1366}
1367
1368static int rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1369{
1370 if (cArgs != 1)
1371 return VERR_INVALID_PARAMETER;
1372
1373 const char *pcszType = apcszArgs[0];
1374
1375 int rc;
1376
1377 if (!RTStrICmp(pcszType, "F"))
1378 {
1379 pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
1380
1381 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1382 }
1383 else
1384 rc = VERR_NOT_IMPLEMENTED;
1385
1386 return rc;
1387}
1388
1389static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1390{
1391 RT_NOREF(cArgs, apcszArgs);
1392
1393 char szOSInfo[64];
1394 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
1395 if (RT_SUCCESS(rc))
1396 rc = rtFtpServerSendReplyStr(pClient, szOSInfo);
1397
1398 return rc;
1399}
1400
1401static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1402{
1403 if (cArgs != 1)
1404 return VERR_INVALID_PARAMETER;
1405
1406 const char *pcszType = apcszArgs[0];
1407
1408 int rc = VINF_SUCCESS;
1409
1410 if (!RTStrICmp(pcszType, "A"))
1411 {
1412 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
1413 }
1414 else if (!RTStrICmp(pcszType, "I")) /* Image (binary). */
1415 {
1416 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE;
1417 }
1418 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
1419 rc = VERR_NOT_IMPLEMENTED;
1420
1421 if (RT_SUCCESS(rc))
1422 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1423
1424 return rc;
1425}
1426
1427static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1428{
1429 if (cArgs != 1)
1430 return VERR_INVALID_PARAMETER;
1431
1432 const char *pcszUser = apcszArgs[0];
1433 AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
1434
1435 rtFtpServerClientStateReset(&pClient->State);
1436
1437 int rc = rtFtpServerLookupUser(pClient, pcszUser);
1438 if (RT_SUCCESS(rc))
1439 {
1440 pClient->State.pszUser = RTStrDup(pcszUser);
1441 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
1442
1443 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
1444 }
1445 else
1446 {
1447 pClient->State.cFailedLoginAttempts++;
1448
1449 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1450 if (RT_SUCCESS(rc))
1451 rc = rc2;
1452 }
1453
1454 return rc;
1455}
1456
1457
1458/*********************************************************************************************************************************
1459* Internal server functions *
1460*********************************************************************************************************************************/
1461
1462/**
1463 * Parses FTP command arguments handed in by the client.
1464 *
1465 * @returns VBox status code.
1466 * @param pcszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
1467 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
1468 * @param ppapcszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
1469 */
1470static int rtFtpServerCmdArgsParse(const char *pcszCmdParms, uint8_t *pcArgs, char ***ppapcszArgs)
1471{
1472 *pcArgs = 0;
1473 *ppapcszArgs = NULL;
1474
1475 if (!pcszCmdParms) /* No parms given? Bail out early. */
1476 return VINF_SUCCESS;
1477
1478 /** @todo Anything else to do here? */
1479 /** @todo Check if quoting is correct. */
1480
1481 int cArgs = 0;
1482 int rc = RTGetOptArgvFromString(ppapcszArgs, &cArgs, pcszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
1483 if (RT_SUCCESS(rc))
1484 {
1485 if (cArgs <= UINT8_MAX)
1486 {
1487 *pcArgs = (uint8_t)cArgs;
1488 }
1489 else
1490 rc = VERR_INVALID_PARAMETER;
1491 }
1492
1493 return rc;
1494}
1495
1496/**
1497 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
1498 *
1499 * @param ppapcszArgs Argument string array to free.
1500 */
1501static void rtFtpServerCmdArgsFree(char **ppapcszArgs)
1502{
1503 RTGetOptArgvFree(ppapcszArgs);
1504}
1505
1506/**
1507 * Main function for processing client commands for the control connection.
1508 *
1509 * @returns VBox status code.
1510 * @param pClient Client to process commands for.
1511 * @param pcszCmd Command string to parse and handle.
1512 * @param cbCmd Size (in bytes) of command string.
1513 */
1514static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pcszCmd, size_t cbCmd)
1515{
1516 /* Make sure to terminate the string in any case. */
1517 pcszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0';
1518
1519 /* A tiny bit of sanitation. */
1520 RTStrStripL(pcszCmd);
1521
1522 /* First, terminate string by finding the command end marker (telnet style). */
1523 /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
1524 char *pszCmdEnd = RTStrIStr(pcszCmd, "\r\n");
1525 if (pszCmdEnd)
1526 *pszCmdEnd = '\0';
1527
1528 /* Reply which gets sent back to the client. */
1529 RTFTPSERVER_REPLY rcClient = RTFTPSERVER_REPLY_INVALID;
1530
1531 int rcCmd = VINF_SUCCESS;
1532
1533 uint8_t cArgs = 0;
1534 char **papszArgs = NULL;
1535 int rc = rtFtpServerCmdArgsParse(pcszCmd, &cArgs, &papszArgs);
1536 if ( RT_SUCCESS(rc)
1537 && cArgs) /* At least the actual command (without args) must be present. */
1538 {
1539 LogFlowFunc(("Handling command '%s'\n", papszArgs[0]));
1540
1541 unsigned i = 0;
1542 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
1543 {
1544 const RTFTPSERVER_CMD_ENTRY *pCmdEntry = &g_aCmdMap[i];
1545
1546 if (!RTStrICmp(papszArgs[0], pCmdEntry->szCmd))
1547 {
1548 /* Some commands need a valid user before they can be executed. */
1549 if ( pCmdEntry->fNeedsUser
1550 && pClient->State.pszUser == NULL)
1551 {
1552 rcClient = RTFTPSERVER_REPLY_NOT_LOGGED_IN;
1553 break;
1554 }
1555
1556 /* Save timestamp of last command sent. */
1557 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1558
1559 /* Hand in arguments only without the actual command. */
1560 rcCmd = pCmdEntry->pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
1561 if (RT_FAILURE(rcCmd))
1562 {
1563 LogFunc(("Handling command '%s' failed with %Rrc\n", papszArgs[0], rcCmd));
1564
1565 switch (rcCmd)
1566 {
1567 case VERR_INVALID_PARAMETER:
1568 RT_FALL_THROUGH();
1569 case VERR_INVALID_POINTER:
1570 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
1571 break;
1572
1573 case VERR_NOT_IMPLEMENTED:
1574 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
1575 break;
1576
1577 default:
1578 break;
1579 }
1580 }
1581 break;
1582 }
1583 }
1584
1585 rtFtpServerCmdArgsFree(papszArgs);
1586
1587 if (i == RT_ELEMENTS(g_aCmdMap))
1588 {
1589 LogFlowFunc(("Command not implemented\n"));
1590 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
1591 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
1592 }
1593
1594 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
1595 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
1596 if (fDisconnect)
1597 {
1598 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser);
1599
1600 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
1601 rcClient = RTFTPSERVER_REPLY_CLOSING_CTRL_CONN;
1602 }
1603 }
1604 else
1605 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
1606
1607 if (rcClient != RTFTPSERVER_REPLY_INVALID)
1608 {
1609 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
1610 if (RT_SUCCESS(rc))
1611 rc = rc2;
1612 }
1613
1614 LogFlowFuncLeaveRC(rc);
1615 return rc;
1616}
1617
1618/**
1619 * Main loop for processing client commands.
1620 *
1621 * @returns VBox status code.
1622 * @param pClient Client to process commands for.
1623 */
1624static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
1625{
1626 int rc;
1627
1628 size_t cbRead;
1629 char szCmd[RTFTPSERVER_MAX_CMD_LEN + 1];
1630
1631 for (;;)
1632 {
1633 rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */
1634 if (RT_SUCCESS(rc))
1635 {
1636 rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
1637 if ( RT_SUCCESS(rc)
1638 && cbRead)
1639 {
1640 AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW);
1641 rc = rtFtpServerProcessCommands(pClient, szCmd, cbRead);
1642 }
1643 }
1644 else
1645 {
1646 if (rc == VERR_TIMEOUT)
1647 rc = VINF_SUCCESS;
1648
1649 if (RT_FAILURE(rc))
1650 break;
1651 }
1652
1653 /*
1654 * Handle data connection replies.
1655 */
1656 if (pClient->pDataConn)
1657 {
1658 if ( ASMAtomicReadBool(&pClient->pDataConn->fStarted)
1659 && ASMAtomicReadBool(&pClient->pDataConn->fStopped))
1660 {
1661 Assert(pClient->pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS);
1662
1663 rc = rtFtpServerDataConnStop(pClient->pDataConn);
1664 if (RT_SUCCESS(rc))
1665 {
1666 rtFtpServerDataConnDestroy(pClient->pDataConn);
1667 pClient->pDataConn = NULL;
1668 }
1669 }
1670 }
1671 }
1672
1673 /* Make sure to destroy all data connections. */
1674 rtFtpServerDataConnDestroy(pClient->pDataConn);
1675 pClient->pDataConn = NULL;
1676
1677 LogFlowFuncLeaveRC(rc);
1678 return rc;
1679}
1680
1681/**
1682 * Resets the client's state.
1683 *
1684 * @param pState Client state to reset.
1685 */
1686static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
1687{
1688 LogFlowFuncEnter();
1689
1690 RTStrFree(pState->pszUser);
1691 pState->pszUser = NULL;
1692
1693 pState->cFailedLoginAttempts = 0;
1694 pState->tsLastCmdMs = RTTimeMilliTS();
1695 pState->enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
1696 pState->enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
1697}
1698
1699/**
1700 * Per-client thread for serving the server's control connection.
1701 *
1702 * @returns VBox status code.
1703 * @param hSocket Socket handle to use for the control connection.
1704 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
1705 */
1706static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
1707{
1708 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
1709 RTFTPSERVER_VALID_RETURN(pThis);
1710
1711 RTFTPSERVERCLIENT Client;
1712 RT_ZERO(Client);
1713
1714 Client.pServer = pThis;
1715 Client.hSocket = hSocket;
1716
1717 LogFlowFunc(("New client connected\n"));
1718
1719 rtFtpServerClientStateReset(&Client.State);
1720
1721 /*
1722 * Send welcome message.
1723 * Note: Some clients (like FileZilla / Firefox) expect a message together with the reply code,
1724 * so make sure to include at least *something*.
1725 */
1726 int rc = rtFtpServerSendReplyRcEx(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER,
1727 "Welcome!");
1728 if (RT_SUCCESS(rc))
1729 {
1730 ASMAtomicIncU32(&pThis->cClients);
1731
1732 rc = rtFtpServerProcessCommands(&Client);
1733
1734 ASMAtomicDecU32(&pThis->cClients);
1735 }
1736
1737 rtFtpServerClientStateReset(&Client.State);
1738
1739 return rc;
1740}
1741
1742RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
1743 PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
1744{
1745 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
1746 AssertPtrReturn(pcszAddress, VERR_INVALID_POINTER);
1747 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1748 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1749 /* pvUser is optional. */
1750
1751 int rc;
1752
1753 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
1754 if (pThis)
1755 {
1756 pThis->u32Magic = RTFTPSERVER_MAGIC;
1757 pThis->Callbacks = *pCallbacks;
1758 pThis->pvUser = pvUser;
1759 pThis->cbUser = cbUser;
1760
1761 rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
1762 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1763 if (RT_SUCCESS(rc))
1764 {
1765 *phFTPServer = (RTFTPSERVER)pThis;
1766 }
1767 }
1768 else
1769 rc = VERR_NO_MEMORY;
1770
1771 return rc;
1772}
1773
1774RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
1775{
1776 if (hFTPServer == NIL_RTFTPSERVER)
1777 return VINF_SUCCESS;
1778
1779 PRTFTPSERVERINTERNAL pThis = hFTPServer;
1780 RTFTPSERVER_VALID_RETURN(pThis);
1781
1782 AssertPtr(pThis->pTCPServer);
1783
1784 int rc = RTTcpServerDestroy(pThis->pTCPServer);
1785 if (RT_SUCCESS(rc))
1786 {
1787 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
1788
1789 RTMemFree(pThis);
1790 }
1791
1792 return rc;
1793}
1794
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