VirtualBox

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

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

IPRT/FTP: Build fix. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 42.2 KB
Line 
1/* $Id: ftp-server.cpp 82728 2020-01-13 15:00:53Z 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 * - No support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++).
32 * - No FTPS / SFTP support.
33 * - No passive mode ("PASV") support.
34 * - No IPv6 support.
35 * - No proxy support.
36 * - No FXP support.
37 */
38
39
40/*********************************************************************************************************************************
41* Header Files *
42*********************************************************************************************************************************/
43#define LOG_GROUP RTLOGGROUP_FTP
44#include <iprt/ftp.h>
45#include "internal/iprt.h"
46#include "internal/magics.h"
47
48#include <iprt/asm.h>
49#include <iprt/assert.h>
50#include <iprt/errcore.h>
51#include <iprt/file.h> /* For file mode flags. */
52#include <iprt/getopt.h>
53#include <iprt/mem.h>
54#include <iprt/log.h>
55#include <iprt/path.h>
56#include <iprt/poll.h>
57#include <iprt/socket.h>
58#include <iprt/string.h>
59#include <iprt/system.h>
60#include <iprt/tcp.h>
61
62
63/*********************************************************************************************************************************
64* Structures and Typedefs *
65*********************************************************************************************************************************/
66/**
67 * Internal FTP server instance.
68 */
69typedef struct RTFTPSERVERINTERNAL
70{
71 /** Magic value. */
72 uint32_t u32Magic;
73 /** Callback table. */
74 RTFTPSERVERCALLBACKS Callbacks;
75 /** Pointer to TCP server instance. */
76 PRTTCPSERVER pTCPServer;
77 /** Number of currently connected clients. */
78 uint32_t cClients;
79} RTFTPSERVERINTERNAL;
80/** Pointer to an internal FTP server instance. */
81typedef RTFTPSERVERINTERNAL *PRTFTPSERVERINTERNAL;
82
83
84/*********************************************************************************************************************************
85* Defined Constants And Macros *
86*********************************************************************************************************************************/
87/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
88#define RTFTPSERVER_VALID_RETURN_RC(hFTPServer, a_rc) \
89 do { \
90 AssertPtrReturn((hFTPServer), (a_rc)); \
91 AssertReturn((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC, (a_rc)); \
92 } while (0)
93
94/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
95#define RTFTPSERVER_VALID_RETURN(hFTPServer) RTFTPSERVER_VALID_RETURN_RC((hFTPServer), VERR_INVALID_HANDLE)
96
97/** Validates a handle and returns (void) if not valid. */
98#define RTFTPSERVER_VALID_RETURN_VOID(hFTPServer) \
99 do { \
100 AssertPtrReturnVoid(hFTPServer); \
101 AssertReturnVoid((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC); \
102 } while (0)
103
104/** Supported FTP server command IDs.
105 * Alphabetically, named after their official command names. */
106typedef enum RTFTPSERVER_CMD
107{
108 /** Invalid command, do not use. Always must come first. */
109 RTFTPSERVER_CMD_INVALID = 0,
110 /** Aborts the current command on the server. */
111 RTFTPSERVER_CMD_ABOR,
112 /** Changes the current working directory. */
113 RTFTPSERVER_CMD_CDUP,
114 /** Changes the current working directory. */
115 RTFTPSERVER_CMD_CWD,
116 /** Lists a directory. */
117 RTFTPSERVER_CMD_LIST,
118 /** Sets the transfer mode. */
119 RTFTPSERVER_CMD_MODE,
120 /** Sends a nop ("no operation") to the server. */
121 RTFTPSERVER_CMD_NOOP,
122 /** Sets the password for authentication. */
123 RTFTPSERVER_CMD_PASS,
124 /** Sets the port to use for the data connection. */
125 RTFTPSERVER_CMD_PORT,
126 /** Gets the current working directory. */
127 RTFTPSERVER_CMD_PWD,
128 /** Terminates the session (connection). */
129 RTFTPSERVER_CMD_QUIT,
130 /** Retrieves a specific file. */
131 RTFTPSERVER_CMD_RETR,
132 /** Retrieves the size of a file. */
133 RTFTPSERVER_CMD_SIZE,
134 /** Retrieves the current status of a transfer. */
135 RTFTPSERVER_CMD_STAT,
136 /** Gets the server's OS info. */
137 RTFTPSERVER_CMD_SYST,
138 /** Sets the (data) representation type. */
139 RTFTPSERVER_CMD_TYPE,
140 /** Sets the user name for authentication. */
141 RTFTPSERVER_CMD_USER,
142 /** End marker. */
143 RTFTPSERVER_CMD_LAST,
144 /** The usual 32-bit hack. */
145 RTFTPSERVER_CMD_32BIT_HACK = 0x7fffffff
146} RTFTPSERVER_CMD;
147
148/**
149 * Structure for maintaining a single data connection.
150 */
151typedef struct RTFTPSERVERDATACONN
152{
153 /** Data connection IP. */
154 RTNETADDRIPV4 Addr;
155 /** Data connection port number. */
156 uint16_t uPort;
157 /** The current data socket to use.
158 * Can be NIL_RTSOCKET if no data port has been specified (yet) or has been closed. */
159 RTSOCKET hSocket;
160 /** Thread serving the data connection. */
161 RTTHREAD hThread;
162 /** Thread started indicator. */
163 volatile bool fStarted;
164 /** Thread stop indicator. */
165 volatile bool fStop;
166 char szFile[RTPATH_MAX];
167} RTFTPSERVERDATACONN;
168/** Pointer to a data connection struct. */
169typedef RTFTPSERVERDATACONN *PRTFTPSERVERDATACONN;
170
171/**
172 * Structure for maintaining an internal FTP server client.
173 */
174typedef struct RTFTPSERVERCLIENT
175{
176 /** Pointer to internal server state. */
177 PRTFTPSERVERINTERNAL pServer;
178 /** Socket handle the client is bound to. */
179 RTSOCKET hSocket;
180 /** Actual client state. */
181 RTFTPSERVERCLIENTSTATE State;
182 /** Data connection information.
183 * At the moment we only allow one data connection per client at a time. */
184 RTFTPSERVERDATACONN DataConn;
185} RTFTPSERVERCLIENT;
186/** Pointer to an internal FTP server client state. */
187typedef RTFTPSERVERCLIENT *PRTFTPSERVERCLIENT;
188
189/** Function pointer declaration for a specific FTP server command handler. */
190typedef DECLCALLBACK(int) FNRTFTPSERVERCMD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs);
191/** Pointer to a FNRTFTPSERVERCMD(). */
192typedef FNRTFTPSERVERCMD *PFNRTFTPSERVERCMD;
193
194/** Handles a FTP server callback with no arguments and returns. */
195#define RTFTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
196 do \
197 { \
198 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
199 if (pCallbacks->a_Name) \
200 { \
201 RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
202 return pCallbacks->a_Name(&Data); \
203 } \
204 else \
205 return VERR_NOT_IMPLEMENTED; \
206 } while (0)
207
208/** Handles a FTP server callback with no arguments and sets rc accordingly. */
209#define RTFTPSERVER_HANDLE_CALLBACK(a_Name) \
210 do \
211 { \
212 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
213 if (pCallbacks->a_Name) \
214 { \
215 RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
216 rc = pCallbacks->a_Name(&Data); \
217 } \
218 else \
219 rc = VERR_NOT_IMPLEMENTED; \
220 } while (0)
221
222/** Handles a FTP server callback with arguments and sets rc accordingly. */
223#define RTFTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
224 do \
225 { \
226 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
227 if (pCallbacks->a_Name) \
228 { \
229 RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
230 rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
231 } \
232 else \
233 rc = VERR_NOT_IMPLEMENTED; \
234 } while (0)
235
236/** Handles a FTP server callback with arguments and returns. */
237#define RTFTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
238 do \
239 { \
240 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
241 if (pCallbacks->a_Name) \
242 { \
243 RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
244 return pCallbacks->a_Name(&Data, __VA_ARGS__); \
245 } \
246 else \
247 return VERR_NOT_IMPLEMENTED; \
248 } while (0)
249
250
251/*********************************************************************************************************************************
252* Defined Constants And Macros *
253*********************************************************************************************************************************/
254
255static int rtFtpServerDataPortOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort);
256
257/**
258 * Function prototypes for command handlers.
259 */
260static FNRTFTPSERVERCMD rtFtpServerHandleABOR;
261static FNRTFTPSERVERCMD rtFtpServerHandleCDUP;
262static FNRTFTPSERVERCMD rtFtpServerHandleCWD;
263static FNRTFTPSERVERCMD rtFtpServerHandleLIST;
264static FNRTFTPSERVERCMD rtFtpServerHandleMODE;
265static FNRTFTPSERVERCMD rtFtpServerHandleNOOP;
266static FNRTFTPSERVERCMD rtFtpServerHandlePASS;
267static FNRTFTPSERVERCMD rtFtpServerHandlePORT;
268static FNRTFTPSERVERCMD rtFtpServerHandlePWD;
269static FNRTFTPSERVERCMD rtFtpServerHandleQUIT;
270static FNRTFTPSERVERCMD rtFtpServerHandleRETR;
271static FNRTFTPSERVERCMD rtFtpServerHandleSIZE;
272static FNRTFTPSERVERCMD rtFtpServerHandleSTAT;
273static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
274static FNRTFTPSERVERCMD rtFtpServerHandleTYPE;
275static FNRTFTPSERVERCMD rtFtpServerHandleUSER;
276
277
278/**
279 * Structure for maintaining a single command entry for the command table.
280 */
281typedef struct RTFTPSERVER_CMD_ENTRY
282{
283 /** Command ID. */
284 RTFTPSERVER_CMD enmCmd;
285 /** Command represented as ASCII string. */
286 char szCmd[RTFTPSERVER_MAX_CMD_LEN];
287 /** Function pointer invoked to handle the command. */
288 PFNRTFTPSERVERCMD pfnCmd;
289} RTFTPSERVER_CMD_ENTRY;
290
291/**
292 * Table of handled commands.
293 */
294const RTFTPSERVER_CMD_ENTRY g_aCmdMap[] =
295{
296 { RTFTPSERVER_CMD_ABOR, "ABOR", rtFtpServerHandleABOR },
297 { RTFTPSERVER_CMD_CDUP, "CDUP", rtFtpServerHandleCDUP },
298 { RTFTPSERVER_CMD_CWD, "CWD", rtFtpServerHandleCWD },
299 { RTFTPSERVER_CMD_LIST, "LIST", rtFtpServerHandleLIST },
300 { RTFTPSERVER_CMD_MODE, "MODE", rtFtpServerHandleMODE },
301 { RTFTPSERVER_CMD_NOOP, "NOOP", rtFtpServerHandleNOOP },
302 { RTFTPSERVER_CMD_PASS, "PASS", rtFtpServerHandlePASS },
303 { RTFTPSERVER_CMD_PORT, "PORT", rtFtpServerHandlePORT },
304 { RTFTPSERVER_CMD_PWD, "PWD", rtFtpServerHandlePWD },
305 { RTFTPSERVER_CMD_QUIT, "QUIT", rtFtpServerHandleQUIT },
306 { RTFTPSERVER_CMD_RETR, "RETR", rtFtpServerHandleRETR },
307 { RTFTPSERVER_CMD_SIZE, "SIZE", rtFtpServerHandleSIZE },
308 { RTFTPSERVER_CMD_STAT, "STAT", rtFtpServerHandleSTAT },
309 { RTFTPSERVER_CMD_SYST, "SYST", rtFtpServerHandleSYST },
310 { RTFTPSERVER_CMD_TYPE, "TYPE", rtFtpServerHandleTYPE },
311 { RTFTPSERVER_CMD_USER, "USER", rtFtpServerHandleUSER },
312 { RTFTPSERVER_CMD_LAST, "", NULL }
313};
314
315
316/*********************************************************************************************************************************
317* Protocol Functions *
318*********************************************************************************************************************************/
319
320/**
321 * Replies a (three digit) reply code back to the client.
322 *
323 * @returns VBox status code.
324 * @param pClient Client to reply to.
325 * @param enmReply Reply code to send.
326 */
327static int rtFtpServerSendReplyRc(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply)
328{
329 char szReply[32];
330 RTStrPrintf2(szReply, sizeof(szReply), "%RU32\r\n", enmReply);
331
332 return RTTcpWrite(pClient->hSocket, szReply, strlen(szReply) + 1);
333}
334
335/**
336 * Replies a string back to the client.
337 *
338 * @returns VBox status code.
339 * @param pClient Client to reply to.
340 * @param pcszFormat Format to reply.
341 * @param ... Format arguments.
342 */
343static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszFormat, ...)
344{
345 va_list args;
346 va_start(args, pcszFormat);
347 char *psz = NULL;
348 const int cch = RTStrAPrintfV(&psz, pcszFormat, args);
349 va_end(args);
350 AssertReturn(cch > 0, VERR_NO_MEMORY);
351
352 int rc = RTStrAAppend(&psz, "\r\n");
353 AssertRCReturn(rc, rc);
354
355 rc = RTTcpWrite(pClient->hSocket, psz, strlen(psz) + 1 /* Include termination */);
356
357 RTStrFree(psz);
358
359 return rc;
360}
361
362/**
363 * Looks up an user account.
364 *
365 * @returns VBox status code, or VERR_NOT_FOUND if user has not been found.
366 * @param pClient Client to look up user for.
367 * @param pcszUser User name to look up.
368 */
369static int rtFtpServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pcszUser)
370{
371 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pcszUser);
372}
373
374/**
375 * Handles the actual client authentication.
376 *
377 * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed.
378 * @param pClient Client to authenticate.
379 * @param pcszUser User name to authenticate with.
380 * @param pcszPassword Password to authenticate with.
381 */
382static int rtFtpServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pcszUser, const char *pcszPassword)
383{
384 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pcszUser, pcszPassword);
385}
386
387/**
388 * Converts a RTFSOBJINFO struct to a string.
389 *
390 * @returns VBox status code.
391 * @param pObjInfo RTFSOBJINFO object to convert.
392 * @param pszFsObjInfo Where to store the output string.
393 * @param cbFsObjInfo Size of the output string in bytes.
394 */
395static int rtFtpServerFsObjInfoToStr(PRTFSOBJINFO pObjInfo, char *pszFsObjInfo, size_t cbFsObjInfo)
396{
397 RTFMODE fMode = pObjInfo->Attr.fMode;
398 char chFileType;
399 switch (fMode & RTFS_TYPE_MASK)
400 {
401 case RTFS_TYPE_FIFO: chFileType = 'f'; break;
402 case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
403 case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
404 case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
405 case RTFS_TYPE_FILE: chFileType = '-'; break;
406 case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
407 case RTFS_TYPE_SOCKET: chFileType = 's'; break;
408 case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
409 default: chFileType = '?'; break;
410 }
411
412 char szTimeBirth[RTTIME_STR_LEN];
413 char szTimeChange[RTTIME_STR_LEN];
414 char szTimeModification[RTTIME_STR_LEN];
415 char szTimeAccess[RTTIME_STR_LEN];
416
417#define INFO_TO_STR(a_Format, ...) \
418 do \
419 { \
420 const ssize_t cchSize = RTStrPrintf2(szTemp, sizeof(szTemp), a_Format, __VA_ARGS__); \
421 AssertReturn(cchSize > 0, VERR_BUFFER_OVERFLOW); \
422 const int rc2 = RTStrCat(pszFsObjInfo, cbFsObjInfo, szTemp); \
423 AssertRCReturn(rc2, rc2); \
424 } while (0);
425
426 char szTemp[32];
427
428 INFO_TO_STR("%c", chFileType);
429 INFO_TO_STR("%c%c%c",
430 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
431 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
432 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
433 INFO_TO_STR("%c%c%c",
434 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
435 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
436 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
437 INFO_TO_STR("%c%c%c",
438 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
439 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
440 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
441
442 INFO_TO_STR( " %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
443 fMode & RTFS_DOS_READONLY ? 'R' : '-',
444 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
445 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
446 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
447 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
448 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
449 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
450 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
451 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
452 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
453 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
454 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
455 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
456 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
457
458 INFO_TO_STR( " %d %4d %4d %10lld %10lld",
459 pObjInfo->Attr.u.Unix.cHardlinks,
460 pObjInfo->Attr.u.Unix.uid,
461 pObjInfo->Attr.u.Unix.gid,
462 pObjInfo->cbObject,
463 pObjInfo->cbAllocated);
464
465 INFO_TO_STR( " %s %s %s %s",
466 RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
467 RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
468 RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
469 RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
470
471#undef INFO_TO_STR
472
473 return VINF_SUCCESS;
474}
475
476/**
477 * Parses a string which consists of an IPv4 (ww,xx,yy,zz) and a port number (hi,lo), all separated by comma delimiters.
478 * See RFC 959, 4.1.2.
479 *
480 * @returns VBox status code.
481 * @param pcszStr String to parse.
482 * @param pAddr Where to store the IPv4 address on success.
483 * @param puPort Where to store the port number on success.
484 */
485static int rtFtpParseHostAndPort(const char *pcszStr, PRTNETADDRIPV4 pAddr, uint16_t *puPort)
486{
487 AssertPtrReturn(pcszStr, VERR_INVALID_POINTER);
488 AssertPtrReturn(pAddr, VERR_INVALID_POINTER);
489 AssertPtrReturn(puPort, VERR_INVALID_POINTER);
490
491 char *pszNext;
492 int rc;
493
494 /* Parse IP (v4). */
495 /** @todo I don't think IPv6 ever will be a thing here, or will it? */
496 rc = RTStrToUInt8Ex(pcszStr, &pszNext, 10, &pAddr->au8[0]);
497 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
498 return VERR_INVALID_PARAMETER;
499 if (*pszNext++ != ',')
500 return VERR_INVALID_PARAMETER;
501
502 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]);
503 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
504 return VERR_INVALID_PARAMETER;
505 if (*pszNext++ != ',')
506 return VERR_INVALID_PARAMETER;
507
508 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]);
509 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
510 return VERR_INVALID_PARAMETER;
511 if (*pszNext++ != ',')
512 return VERR_INVALID_PARAMETER;
513
514 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]);
515 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
516 return VERR_INVALID_PARAMETER;
517 if (*pszNext++ != ',')
518 return VERR_INVALID_PARAMETER;
519
520 /* Parse port. */
521 uint8_t uPortHi;
522 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortHi);
523 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
524 return VERR_INVALID_PARAMETER;
525 if (*pszNext++ != ',')
526 return VERR_INVALID_PARAMETER;
527 uint8_t uPortLo;
528 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortLo);
529 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
530 return VERR_INVALID_PARAMETER;
531
532 *puPort = RT_MAKE_U16(uPortLo, uPortHi);
533
534 return rc;
535}
536
537/**
538 * Opens a data connection to the client.
539 *
540 * @returns VBox status code.
541 * @param pDataConn Data connection to open.
542 * @param pAddr Address for the data connection.
543 * @param uPort Port for the data connection.
544 */
545static int rtFtpServerDataPortOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort)
546{
547 RT_NOREF(pAddr);
548
549 char szAddress[32];
550 const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8",
551 pAddr->au8[0], pAddr->au8[1], pAddr->au8[2], pAddr->au8[3]);
552 AssertReturn(cchAdddress > 0, VERR_NO_MEMORY);
553
554 return RTTcpClientConnect(szAddress, uPort, &pDataConn->hSocket);
555}
556
557/**
558 * Closes a data connection to the client.
559 *
560 * @returns VBox status code.
561 * @param pDataConn Data connection to close.
562 */
563static int rtFtpServerDataPortClose(PRTFTPSERVERDATACONN pDataConn)
564{
565 int rc = VINF_SUCCESS;
566
567 if (pDataConn->hSocket != NIL_RTSOCKET)
568 {
569 rc = RTTcpClientClose(pDataConn->hSocket);
570 pDataConn->hSocket = NIL_RTSOCKET;
571 }
572
573 return rc;
574}
575
576/**
577 * Thread serving a data connection.
578 *
579 * @returns VBox status code.
580 * @param ThreadSelf Thread handle. Unused at the moment.
581 * @param pvUser Pointer to user-provided data. Of type PRTFTPSERVERCLIENT.
582 */
583static DECLCALLBACK(int) rtFtpServerDataConnThread(RTTHREAD ThreadSelf, void *pvUser)
584{
585 RT_NOREF(ThreadSelf);
586
587 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
588 AssertPtr(pClient);
589
590 PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn;
591
592 int rc = rtFtpServerDataPortOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort);
593 if (RT_FAILURE(rc))
594 return rc;
595
596 uint32_t cbBuf = _64K; /** @todo Improve this. */
597 void *pvBuf = RTMemAlloc(cbBuf);
598 if (!pvBuf)
599 return VERR_NO_MEMORY;
600
601 pDataConn->fStop = false;
602 pDataConn->fStarted = true;
603
604 RTThreadUserSignal(RTThreadSelf());
605
606 const char *pcszFile = pDataConn->szFile;
607
608 void *pvHandle = NULL; /* Opaque handle known to the actual implementation. */
609
610 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileOpen, pcszFile,
611 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pvHandle);
612 if (RT_SUCCESS(rc))
613 {
614 do
615 {
616 size_t cbRead = 0;
617 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead);
618 if (RT_SUCCESS(rc))
619 rc = RTTcpWrite(pClient->DataConn.hSocket, pvBuf, cbRead);
620
621 if ( !cbRead
622 || ASMAtomicReadBool(&pDataConn->fStop))
623 break;
624 }
625 while (RT_SUCCESS(rc));
626
627 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle);
628 }
629
630 rtFtpServerDataPortClose(&pClient->DataConn);
631
632 RTMemFree(pvBuf);
633 pvBuf = NULL;
634
635 return rc;
636}
637
638/**
639 * Opens a data connection to the client.
640 *
641 * @returns VBox status code.
642 * @param pClient Client to open data connection for.
643 * @param pDataConn Data connection to open.
644 */
645static int rtFtpServerDataConnCreate(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn)
646{
647 int rc = RTThreadCreate(&pDataConn->hThread, rtFtpServerDataConnThread,
648 pClient, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
649 "ftpdata");
650 if (RT_SUCCESS(rc))
651 {
652 int rc2 = RTThreadUserWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */);
653 AssertRC(rc2);
654
655 if (!pDataConn->fStarted) /* Did the thread indicate that it started correctly? */
656 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
657 }
658
659 return rc;
660}
661
662/**
663 * Closes a data connection to the client.
664 *
665 * @returns VBox status code.
666 * @param pClient Client to close data connection for.
667 * @param pDataConn Data connection to close.
668 */
669static int rtFtpServerDataConnDestroy(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn)
670{
671 RT_NOREF(pClient);
672
673 if (pDataConn->hThread == NIL_RTTHREAD)
674 return VINF_SUCCESS;
675
676 LogFlowFuncEnter();
677
678 /* Set stop indicator. */
679 pDataConn->fStop = true;
680
681 int rcThread = VERR_WRONG_ORDER;
682 int rc = RTThreadWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */, &rcThread);
683 if (RT_SUCCESS(rc))
684 {
685 if (pDataConn->hSocket != NIL_RTSOCKET)
686 {
687 RTTcpClientClose(pDataConn->hSocket);
688 pDataConn->hSocket = NIL_RTSOCKET;
689 }
690
691 pDataConn->fStarted = false;
692 pDataConn->hThread = NIL_RTTHREAD;
693
694 rc = rcThread;
695 }
696
697 return rc;
698}
699
700
701/*********************************************************************************************************************************
702* Command Protocol Handlers *
703*********************************************************************************************************************************/
704
705static int rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
706{
707 RT_NOREF(cArgs, apcszArgs);
708
709 int rc = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
710 if (RT_SUCCESS(rc))
711 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
712
713 return rc;
714}
715
716static int rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
717{
718 RT_NOREF(cArgs, apcszArgs);
719
720 int rc;
721
722 RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp);
723
724 if (RT_SUCCESS(rc))
725 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
726
727 return rc;
728}
729
730static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
731{
732 if (cArgs != 1)
733 return VERR_INVALID_PARAMETER;
734
735 int rc;
736
737 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, apcszArgs[0]);
738
739 if (RT_SUCCESS(rc))
740 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
741
742 return rc;
743}
744
745static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
746{
747 RT_NOREF(cArgs, apcszArgs);
748
749 int rc;
750
751 void *pvData = NULL;
752 size_t cbData = 0;
753
754 /* The first argument might indicate a directory to list. */
755 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnList,
756 cArgs == 1
757 ? apcszArgs[0] : NULL, &pvData, &cbData);
758
759 if (RT_SUCCESS(rc))
760 {
761 RTMemFree(pvData);
762 }
763
764 return rc;
765}
766
767static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
768{
769 RT_NOREF(pClient, cArgs, apcszArgs);
770
771 /** @todo Anything to do here? */
772 return VINF_SUCCESS;
773}
774
775static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
776{
777 RT_NOREF(cArgs, apcszArgs);
778
779 /* Save timestamp of last command sent. */
780 pClient->State.tsLastCmdMs = RTTimeMilliTS();
781
782 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
783}
784
785static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
786{
787 if (cArgs != 1)
788 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
789
790 const char *pcszPassword = apcszArgs[0];
791 AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
792
793 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
794 if (RT_SUCCESS(rc))
795 {
796 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
797 }
798 else
799 {
800 pClient->State.cFailedLoginAttempts++;
801
802 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
803 if (RT_SUCCESS(rc))
804 rc = rc2;
805 }
806
807 return rc;
808}
809
810static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
811{
812 if (cArgs != 1)
813 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
814
815 /* Only allow one data connection per client at a time. */
816 rtFtpServerDataPortClose(&pClient->DataConn);
817
818 int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConn.Addr, &pClient->DataConn.uPort);
819 if (RT_SUCCESS(rc))
820 {
821 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
822 }
823 else
824 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
825
826 return rc;
827}
828
829static int rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
830{
831 RT_NOREF(cArgs, apcszArgs);
832
833 int rc;
834
835 char szPWD[RTPATH_MAX];
836
837 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
838
839 if (RT_SUCCESS(rc))
840 rc = rtFtpServerSendReplyStr(pClient, szPWD);
841
842 return rc;
843}
844
845static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
846{
847 RT_NOREF(cArgs, apcszArgs);
848
849 return rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
850}
851
852static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
853{
854 if (cArgs != 1)
855 return VERR_INVALID_PARAMETER;
856
857 int rc;
858
859 const char *pcszPath = apcszArgs[0];
860
861 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
862
863 if (RT_SUCCESS(rc))
864 {
865 rc = RTStrCopy(pClient->DataConn.szFile, sizeof(pClient->DataConn.szFile), pcszPath);
866 if (RT_SUCCESS(rc))
867 {
868 rc = rtFtpServerDataConnCreate(pClient, &pClient->DataConn);
869 if (RT_SUCCESS(rc))
870 {
871 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
872 }
873 }
874 }
875
876 if (RT_FAILURE(rc))
877 {
878 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
879 AssertRC(rc2);
880 }
881
882 return rc;
883}
884
885static int rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
886{
887 if (cArgs != 1)
888 return VERR_INVALID_PARAMETER;
889
890 int rc;
891
892 const char *pcszPath = apcszArgs[0];
893 uint64_t uSize = 0;
894
895 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pcszPath, &uSize);
896
897 if (RT_SUCCESS(rc))
898 {
899 rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
900 }
901 else
902 {
903 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
904 AssertRC(rc2);
905 }
906
907 return rc;
908}
909
910static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
911{
912 if (cArgs != 1)
913 return VERR_INVALID_PARAMETER;
914
915 int rc;
916
917 RTFSOBJINFO objInfo;
918 RT_ZERO(objInfo);
919
920 const char *pcszPath = apcszArgs[0];
921
922 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, &objInfo);
923
924 if (RT_SUCCESS(rc))
925 {
926 char szFsObjInfo[_4K]; /** @todo Check this size. */
927 rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
928 if (RT_SUCCESS(rc))
929 {
930 char szFsPathInfo[RTPATH_MAX + 16];
931 const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pcszPath), pcszPath);
932 if (cchPathInfo > 0)
933 {
934 rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
935 if (RT_SUCCESS(rc))
936 rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
937 }
938 else
939 rc = VERR_BUFFER_OVERFLOW;
940 }
941 }
942
943 if (RT_FAILURE(rc))
944 {
945 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
946 AssertRC(rc2);
947 }
948
949 return rc;
950}
951
952static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
953{
954 RT_NOREF(cArgs, apcszArgs);
955
956 char szOSInfo[64];
957 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
958 if (RT_SUCCESS(rc))
959 rc = rtFtpServerSendReplyStr(pClient, szOSInfo);
960
961 return rc;
962}
963
964static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
965{
966 if (cArgs != 1)
967 return VERR_INVALID_PARAMETER;
968
969 const char *pcszType = apcszArgs[0];
970
971 int rc = VINF_SUCCESS;
972
973 if (!RTStrICmp(pcszType, "A")) /* ASCII (can be 7 or 8 bits). */
974 {
975 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
976 }
977 else if (!RTStrICmp(pcszType, "I")) /* Image (binary). */
978 {
979 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE;
980 }
981 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
982 rc = VERR_INVALID_PARAMETER;
983
984 return rc;
985}
986
987static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
988{
989 if (cArgs != 1)
990 return VERR_INVALID_PARAMETER;
991
992 const char *pcszUser = apcszArgs[0];
993 AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
994
995 if (pClient->State.pszUser)
996 {
997 RTStrFree(pClient->State.pszUser);
998 pClient->State.pszUser = NULL;
999 }
1000
1001 int rc = rtFtpServerLookupUser(pClient, pcszUser);
1002 if (RT_SUCCESS(rc))
1003 {
1004 pClient->State.pszUser = RTStrDup(pcszUser);
1005 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
1006
1007 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
1008 }
1009 else
1010 {
1011 pClient->State.cFailedLoginAttempts++;
1012
1013 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1014 if (RT_SUCCESS(rc))
1015 rc = rc2;
1016 }
1017
1018 return rc;
1019}
1020
1021
1022/*********************************************************************************************************************************
1023* Internal server functions *
1024*********************************************************************************************************************************/
1025
1026/**
1027 * Parses FTP command arguments handed in by the client.
1028 *
1029 * @returns VBox status code.
1030 * @param pcszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
1031 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
1032 * @param ppapcszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
1033 */
1034static int rtFtpServerCmdArgsParse(const char *pcszCmdParms, uint8_t *pcArgs, char ***ppapcszArgs)
1035{
1036 *pcArgs = 0;
1037 *ppapcszArgs = NULL;
1038
1039 if (!pcszCmdParms) /* No parms given? Bail out early. */
1040 return VINF_SUCCESS;
1041
1042 /** @todo Anything else to do here? */
1043 /** @todo Check if quoting is correct. */
1044
1045 int cArgs = 0;
1046 int rc = RTGetOptArgvFromString(ppapcszArgs, &cArgs, pcszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
1047 if (RT_SUCCESS(rc))
1048 {
1049 if (cArgs <= UINT8_MAX)
1050 {
1051 *pcArgs = (uint8_t)cArgs;
1052 }
1053 else
1054 rc = VERR_INVALID_PARAMETER;
1055 }
1056
1057 return rc;
1058}
1059
1060/**
1061 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
1062 *
1063 * @param ppapcszArgs Argument string array to free.
1064 */
1065static void rtFtpServerCmdArgsFree(char **ppapcszArgs)
1066{
1067 RTGetOptArgvFree(ppapcszArgs);
1068}
1069
1070/**
1071 * Main loop for processing client commands.
1072 *
1073 * @returns VBox status code.
1074 * @param pClient Client to process commands for.
1075 */
1076static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
1077{
1078 int rc;
1079
1080 for (;;)
1081 {
1082 size_t cbRead;
1083 char szCmd[RTFTPSERVER_MAX_CMD_LEN];
1084 rc = RTTcpRead(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
1085 if (RT_SUCCESS(rc))
1086 {
1087 /* Make sure to terminate the string in any case. */
1088 szCmd[RTFTPSERVER_MAX_CMD_LEN - 1] = '\0';
1089
1090 /* A tiny bit of sanitation. */
1091 RTStrStripL(szCmd);
1092
1093 /* First, terminate string by finding the command end marker (telnet style). */
1094 /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
1095 char *pszCmdEnd = RTStrIStr(szCmd, "\r\n");
1096 if (pszCmdEnd)
1097 *pszCmdEnd = '\0';
1098
1099 int rcCmd = VINF_SUCCESS;
1100
1101 uint8_t cArgs = 0;
1102 char **papszArgs = NULL;
1103 rc = rtFtpServerCmdArgsParse(szCmd, &cArgs, &papszArgs);
1104 if ( RT_SUCCESS(rc)
1105 && cArgs) /* At least the actual command (without args) must be present. */
1106 {
1107 unsigned i = 0;
1108 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
1109 {
1110 if (!RTStrICmp(papszArgs[0], g_aCmdMap[i].szCmd))
1111 {
1112 /* Save timestamp of last command sent. */
1113 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1114
1115 rcCmd = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
1116 break;
1117 }
1118 }
1119
1120 rtFtpServerCmdArgsFree(papszArgs);
1121
1122 if (i == RT_ELEMENTS(g_aCmdMap))
1123 {
1124 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
1125 if (RT_SUCCESS(rc))
1126 rc = rc2;
1127
1128 continue;
1129 }
1130
1131 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
1132 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
1133 if (fDisconnect)
1134 {
1135 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CLOSING_CTRL_CONN);
1136 if (RT_SUCCESS(rc))
1137 rc = rc2;
1138
1139 RTFTPSERVER_HANDLE_CALLBACK(pfnOnUserDisconnect);
1140 break;
1141 }
1142
1143 switch (rcCmd)
1144 {
1145 case VERR_INVALID_PARAMETER:
1146 RT_FALL_THROUGH();
1147 case VERR_INVALID_POINTER:
1148 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1149 break;
1150
1151 case VERR_NOT_IMPLEMENTED:
1152 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
1153 break;
1154
1155 default:
1156 break;
1157 }
1158 }
1159 else
1160 {
1161 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1162 if (RT_SUCCESS(rc))
1163 rc = rc2;
1164 }
1165 }
1166 else
1167 {
1168 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_RECOGNIZED);
1169 if (RT_SUCCESS(rc))
1170 rc = rc2;
1171 }
1172 }
1173
1174 /* Make sure to close any open data connections. */
1175 int rc2 = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
1176 if (RT_SUCCESS(rc))
1177 rc = rc2;
1178
1179 return rc;
1180}
1181
1182/**
1183 * Resets the client's state.
1184 *
1185 * @param pState Client state to reset.
1186 */
1187static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
1188{
1189 RTStrFree(pState->pszUser);
1190 pState->pszUser = NULL;
1191
1192 pState->tsLastCmdMs = RTTimeMilliTS();
1193}
1194
1195/**
1196 * Per-client thread for serving the server's control connection.
1197 *
1198 * @returns VBox status code.
1199 * @param hSocket Socket handle to use for the control connection.
1200 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
1201 */
1202static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
1203{
1204 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
1205 RTFTPSERVER_VALID_RETURN(pThis);
1206
1207 RTFTPSERVERCLIENT Client;
1208 RT_ZERO(Client);
1209
1210 Client.pServer = pThis;
1211 Client.hSocket = hSocket;
1212
1213 rtFtpServerClientStateReset(&Client.State);
1214
1215 /* Send welcome message. */
1216 int rc = rtFtpServerSendReplyRc(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER);
1217 if (RT_SUCCESS(rc))
1218 {
1219 ASMAtomicIncU32(&pThis->cClients);
1220
1221 rc = rtFtpServerProcessCommands(&Client);
1222
1223 ASMAtomicDecU32(&pThis->cClients);
1224 }
1225
1226 rtFtpServerClientStateReset(&Client.State);
1227
1228 return rc;
1229}
1230
1231RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
1232 PRTFTPSERVERCALLBACKS pCallbacks)
1233{
1234 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
1235 AssertPtrReturn(pcszAddress, VERR_INVALID_POINTER);
1236 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1237 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1238
1239 int rc;
1240
1241 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
1242 if (pThis)
1243 {
1244 pThis->u32Magic = RTFTPSERVER_MAGIC;
1245 pThis->Callbacks = *pCallbacks;
1246
1247 rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
1248 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1249 if (RT_SUCCESS(rc))
1250 {
1251 *phFTPServer = (RTFTPSERVER)pThis;
1252 }
1253 }
1254 else
1255 rc = VERR_NO_MEMORY;
1256
1257 return rc;
1258}
1259
1260RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
1261{
1262 if (hFTPServer == NIL_RTFTPSERVER)
1263 return VINF_SUCCESS;
1264
1265 PRTFTPSERVERINTERNAL pThis = hFTPServer;
1266 RTFTPSERVER_VALID_RETURN(pThis);
1267
1268 AssertPtr(pThis->pTCPServer);
1269
1270 int rc = RTTcpServerDestroy(pThis->pTCPServer);
1271 if (RT_SUCCESS(rc))
1272 {
1273 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
1274
1275 RTMemFree(pThis);
1276 }
1277
1278 return rc;
1279}
1280
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