VirtualBox

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

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

iprt/cdefs.h,*: Introducing RT_FLEXIBLE_ARRAY_EXTENSION as a g++ hack that allows us to use RT_FLEXIBLE_ARRAY without the compiler going all pendantic on us. Only tested with 10.1.0. bugref:9746

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 82.2 KB
Line 
1/* $Id: ftp-server.cpp 84509 2020-05-25 15:09:24Z 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 directory / file caching yet.
33 * - No support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++).
34 * - No FTPS / SFTP support.
35 * - No passive mode ("PASV") support.
36 * - No IPv6 support.
37 * - No proxy support.
38 * - No FXP support.
39 */
40
41
42/*********************************************************************************************************************************
43* Header Files *
44*********************************************************************************************************************************/
45#define LOG_GROUP RTLOGGROUP_FTP
46#include <iprt/ftp.h>
47#include "internal/iprt.h"
48#include "internal/magics.h"
49
50#include <iprt/asm.h>
51#include <iprt/assert.h>
52#include <iprt/circbuf.h>
53#include <iprt/err.h>
54#include <iprt/file.h> /* For file mode flags. */
55#include <iprt/getopt.h>
56#include <iprt/mem.h>
57#include <iprt/log.h>
58#include <iprt/path.h>
59#include <iprt/poll.h>
60#include <iprt/socket.h>
61#include <iprt/sort.h>
62#include <iprt/string.h>
63#include <iprt/system.h>
64#include <iprt/tcp.h>
65
66
67/*********************************************************************************************************************************
68* Structures and Typedefs *
69*********************************************************************************************************************************/
70/**
71 * Internal FTP server instance.
72 */
73typedef struct RTFTPSERVERINTERNAL
74{
75 /** Magic value. */
76 uint32_t u32Magic;
77 /** Callback table. */
78 RTFTPSERVERCALLBACKS Callbacks;
79 /** Pointer to TCP server instance. */
80 PRTTCPSERVER pTCPServer;
81 /** Number of currently connected clients. */
82 uint32_t cClients;
83 /** Pointer to user-specific data. Optional. */
84 void *pvUser;
85 /** Size of user-specific data. Optional. */
86 size_t cbUser;
87} RTFTPSERVERINTERNAL;
88/** Pointer to an internal FTP server instance. */
89typedef RTFTPSERVERINTERNAL *PRTFTPSERVERINTERNAL;
90
91/**
92 * FTP directory entry.
93 */
94typedef struct RTFTPDIRENTRY
95{
96 /** The information about the entry. */
97 RTFSOBJINFO Info;
98 /** Symbolic link target (allocated after the name). */
99 const char *pszTarget;
100 /** Owner if applicable (allocated after the name). */
101 const char *pszOwner;
102 /** Group if applicable (allocated after the name). */
103 const char *pszGroup;
104 /** The length of szName. */
105 size_t cchName;
106 /** The entry name. */
107 RT_FLEXIBLE_ARRAY_EXTENSION
108 char szName[RT_FLEXIBLE_ARRAY];
109} RTFTPDIRENTRY;
110/** Pointer to a FTP directory entry. */
111typedef RTFTPDIRENTRY *PRTFTPDIRENTRY;
112/** Pointer to a FTP directory entry pointer. */
113typedef PRTFTPDIRENTRY *PPRTFTPDIRENTRY;
114
115/**
116 * Collection of directory entries.
117 * Used for also caching stuff.
118 */
119typedef struct RTFTPDIRCOLLECTION
120{
121 /** Current size of papEntries. */
122 size_t cEntries;
123 /** Memory allocated for papEntries. */
124 size_t cEntriesAllocated;
125 /** Current entries pending sorting and display. */
126 PPRTFTPDIRENTRY papEntries;
127
128 /** Total number of bytes allocated for the above entries. */
129 uint64_t cbTotalAllocated;
130 /** Total number of file content bytes. */
131 uint64_t cbTotalFiles;
132
133} RTFTPDIRCOLLECTION;
134/** Pointer to a directory collection. */
135typedef RTFTPDIRCOLLECTION *PRTFTPDIRCOLLECTION;
136/** Pointer to a directory entry collection pointer. */
137typedef PRTFTPDIRCOLLECTION *PPRTFTPDIRCOLLECTION;
138
139
140/*********************************************************************************************************************************
141* Defined Constants And Macros *
142*********************************************************************************************************************************/
143/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
144#define RTFTPSERVER_VALID_RETURN_RC(hFTPServer, a_rc) \
145 do { \
146 AssertPtrReturn((hFTPServer), (a_rc)); \
147 AssertReturn((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC, (a_rc)); \
148 } while (0)
149
150/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
151#define RTFTPSERVER_VALID_RETURN(hFTPServer) RTFTPSERVER_VALID_RETURN_RC((hFTPServer), VERR_INVALID_HANDLE)
152
153/** Validates a handle and returns (void) if not valid. */
154#define RTFTPSERVER_VALID_RETURN_VOID(hFTPServer) \
155 do { \
156 AssertPtrReturnVoid(hFTPServer); \
157 AssertReturnVoid((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC); \
158 } while (0)
159
160/** Supported FTP server command IDs.
161 * Alphabetically, named after their official command names. */
162typedef enum RTFTPSERVER_CMD
163{
164 /** Invalid command, do not use. Always must come first. */
165 RTFTPSERVER_CMD_INVALID = 0,
166 /** Aborts the current command on the server. */
167 RTFTPSERVER_CMD_ABOR,
168 /** Changes the current working directory. */
169 RTFTPSERVER_CMD_CDUP,
170 /** Changes the current working directory. */
171 RTFTPSERVER_CMD_CWD,
172 /** Reports features supported by the server. */
173 RTFTPSERVER_CMD_FEAT,
174 /** Lists a directory. */
175 RTFTPSERVER_CMD_LIST,
176 /** Sets the transfer mode. */
177 RTFTPSERVER_CMD_MODE,
178 /** Sends a nop ("no operation") to the server. */
179 RTFTPSERVER_CMD_NOOP,
180 /** Sets the password for authentication. */
181 RTFTPSERVER_CMD_PASS,
182 /** Sets the port to use for the data connection. */
183 RTFTPSERVER_CMD_PORT,
184 /** Gets the current working directory. */
185 RTFTPSERVER_CMD_PWD,
186 /** Get options. Needed in conjunction with the FEAT command. */
187 RTFTPSERVER_CMD_OPTS,
188 /** Terminates the session (connection). */
189 RTFTPSERVER_CMD_QUIT,
190 /** Retrieves a specific file. */
191 RTFTPSERVER_CMD_RETR,
192 /** Retrieves the size of a file. */
193 RTFTPSERVER_CMD_SIZE,
194 /** Retrieves the current status of a transfer. */
195 RTFTPSERVER_CMD_STAT,
196 /** Sets the structure type to use. */
197 RTFTPSERVER_CMD_STRU,
198 /** Gets the server's OS info. */
199 RTFTPSERVER_CMD_SYST,
200 /** Sets the (data) representation type. */
201 RTFTPSERVER_CMD_TYPE,
202 /** Sets the user name for authentication. */
203 RTFTPSERVER_CMD_USER,
204 /** End marker. */
205 RTFTPSERVER_CMD_LAST,
206 /** The usual 32-bit hack. */
207 RTFTPSERVER_CMD_32BIT_HACK = 0x7fffffff
208} RTFTPSERVER_CMD;
209
210struct RTFTPSERVERCLIENT;
211
212/**
213 * Structure for maintaining a single data connection.
214 */
215typedef struct RTFTPSERVERDATACONN
216{
217 /** Pointer to associated client of this data connection. */
218 RTFTPSERVERCLIENT *pClient;
219 /** Data connection IP. */
220 RTNETADDRIPV4 Addr;
221 /** Data connection port number. */
222 uint16_t uPort;
223 /** The current data socket to use.
224 * Can be NIL_RTSOCKET if no data port has been specified (yet) or has been closed. */
225 RTSOCKET hSocket;
226 /** Thread serving the data connection. */
227 RTTHREAD hThread;
228 /** Thread started indicator. */
229 volatile bool fStarted;
230 /** Thread stop indicator. */
231 volatile bool fStop;
232 /** Thread stopped indicator. */
233 volatile bool fStopped;
234 /** Overall result when closing the data connection. */
235 int rc;
236 /** Number of command arguments. */
237 uint8_t cArgs;
238 /** Command arguments array. Optional and can be NULL.
239 * Will be free'd by the data connection thread. */
240 char** papszArgs;
241 /** Circular buffer for caching data before writing. */
242 PRTCIRCBUF pCircBuf;
243} RTFTPSERVERDATACONN;
244/** Pointer to a data connection struct. */
245typedef RTFTPSERVERDATACONN *PRTFTPSERVERDATACONN;
246
247/**
248 * Structure for maintaining an internal FTP server client.
249 */
250typedef struct RTFTPSERVERCLIENT
251{
252 /** Pointer to internal server state. */
253 PRTFTPSERVERINTERNAL pServer;
254 /** Socket handle the client is bound to. */
255 RTSOCKET hSocket;
256 /** Actual client state. */
257 RTFTPSERVERCLIENTSTATE State;
258 /** The last set data connection IP. */
259 RTNETADDRIPV4 DataConnAddr;
260 /** The last set data connection port number. */
261 uint16_t uDataConnPort;
262 /** Data connection information.
263 * At the moment we only allow one data connection per client at a time. */
264 PRTFTPSERVERDATACONN pDataConn;
265} RTFTPSERVERCLIENT;
266/** Pointer to an internal FTP server client state. */
267typedef RTFTPSERVERCLIENT *PRTFTPSERVERCLIENT;
268
269/** Function pointer declaration for a specific FTP server command handler. */
270typedef DECLCALLBACK(int) FNRTFTPSERVERCMD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs);
271/** Pointer to a FNRTFTPSERVERCMD(). */
272typedef FNRTFTPSERVERCMD *PFNRTFTPSERVERCMD;
273
274/** Handles a FTP server callback with no arguments and returns. */
275#define RTFTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
276 do \
277 { \
278 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
279 if (pCallbacks->a_Name) \
280 { \
281 RTFTPCALLBACKDATA Data = { &pClient->State }; \
282 return pCallbacks->a_Name(&Data); \
283 } \
284 else \
285 return VERR_NOT_IMPLEMENTED; \
286 } while (0)
287
288/** Handles a FTP server callback with no arguments and sets rc accordingly. */
289#define RTFTPSERVER_HANDLE_CALLBACK(a_Name) \
290 do \
291 { \
292 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
293 if (pCallbacks->a_Name) \
294 { \
295 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
296 rc = pCallbacks->a_Name(&Data); \
297 } \
298 else \
299 rc = VERR_NOT_IMPLEMENTED; \
300 } while (0)
301
302/** Handles a FTP server callback with arguments and sets rc accordingly. */
303#define RTFTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
304 do \
305 { \
306 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
307 if (pCallbacks->a_Name) \
308 { \
309 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
310 rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
311 } \
312 else \
313 rc = VERR_NOT_IMPLEMENTED; \
314 } while (0)
315
316/** Handles a FTP server callback with arguments and returns. */
317#define RTFTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
318 do \
319 { \
320 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
321 if (pCallbacks->a_Name) \
322 { \
323 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
324 return pCallbacks->a_Name(&Data, __VA_ARGS__); \
325 } \
326 else \
327 return VERR_NOT_IMPLEMENTED; \
328 } while (0)
329
330
331/*********************************************************************************************************************************
332* Defined Constants And Macros *
333*********************************************************************************************************************************/
334
335static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort);
336static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn);
337static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn);
338static int rtFtpServerDataConnStart(PRTFTPSERVERDATACONN pDataConn, PFNRTTHREAD pfnThread, uint8_t cArgs, const char * const *apcszArgs);
339static int rtFtpServerDataConnStop(PRTFTPSERVERDATACONN pDataConn);
340static void rtFtpServerDataConnDestroy(PRTFTPSERVERDATACONN pDataConn);
341static int rtFtpServerDataConnFlush(PRTFTPSERVERDATACONN pDataConn);
342
343static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState);
344
345/**
346 * Function prototypes for command handlers.
347 */
348static FNRTFTPSERVERCMD rtFtpServerHandleABOR;
349static FNRTFTPSERVERCMD rtFtpServerHandleCDUP;
350static FNRTFTPSERVERCMD rtFtpServerHandleCWD;
351static FNRTFTPSERVERCMD rtFtpServerHandleFEAT;
352static FNRTFTPSERVERCMD rtFtpServerHandleLIST;
353static FNRTFTPSERVERCMD rtFtpServerHandleMODE;
354static FNRTFTPSERVERCMD rtFtpServerHandleNOOP;
355static FNRTFTPSERVERCMD rtFtpServerHandlePASS;
356static FNRTFTPSERVERCMD rtFtpServerHandlePORT;
357static FNRTFTPSERVERCMD rtFtpServerHandlePWD;
358static FNRTFTPSERVERCMD rtFtpServerHandleOPTS;
359static FNRTFTPSERVERCMD rtFtpServerHandleQUIT;
360static FNRTFTPSERVERCMD rtFtpServerHandleRETR;
361static FNRTFTPSERVERCMD rtFtpServerHandleSIZE;
362static FNRTFTPSERVERCMD rtFtpServerHandleSTAT;
363static FNRTFTPSERVERCMD rtFtpServerHandleSTRU;
364static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
365static FNRTFTPSERVERCMD rtFtpServerHandleTYPE;
366static FNRTFTPSERVERCMD rtFtpServerHandleUSER;
367
368/**
369 * Structure for maintaining a single command entry for the command table.
370 */
371typedef struct RTFTPSERVER_CMD_ENTRY
372{
373 /** Command ID. */
374 RTFTPSERVER_CMD enmCmd;
375 /** Command represented as ASCII string. */
376 char szCmd[RTFTPSERVER_MAX_CMD_LEN];
377 /** Whether the commands needs a logged in (valid) user. */
378 bool fNeedsUser;
379 /** Function pointer invoked to handle the command. */
380 PFNRTFTPSERVERCMD pfnCmd;
381} RTFTPSERVER_CMD_ENTRY;
382/** Pointer to a command entry. */
383typedef RTFTPSERVER_CMD_ENTRY *PRTFTPSERVER_CMD_ENTRY;
384
385/**
386 * Table of handled commands.
387 */
388const RTFTPSERVER_CMD_ENTRY g_aCmdMap[] =
389{
390 { RTFTPSERVER_CMD_ABOR, "ABOR", true, rtFtpServerHandleABOR },
391 { RTFTPSERVER_CMD_CDUP, "CDUP", true, rtFtpServerHandleCDUP },
392 { RTFTPSERVER_CMD_CWD, "CWD", true, rtFtpServerHandleCWD },
393 { RTFTPSERVER_CMD_FEAT, "FEAT", false, rtFtpServerHandleFEAT },
394 { RTFTPSERVER_CMD_LIST, "LIST", true, rtFtpServerHandleLIST },
395 { RTFTPSERVER_CMD_MODE, "MODE", true, rtFtpServerHandleMODE },
396 { RTFTPSERVER_CMD_NOOP, "NOOP", true, rtFtpServerHandleNOOP },
397 { RTFTPSERVER_CMD_PASS, "PASS", false, rtFtpServerHandlePASS },
398 { RTFTPSERVER_CMD_PORT, "PORT", true, rtFtpServerHandlePORT },
399 { RTFTPSERVER_CMD_PWD, "PWD", true, rtFtpServerHandlePWD },
400 { RTFTPSERVER_CMD_OPTS, "OPTS", false, rtFtpServerHandleOPTS },
401 { RTFTPSERVER_CMD_QUIT, "QUIT", false, rtFtpServerHandleQUIT },
402 { RTFTPSERVER_CMD_RETR, "RETR", true, rtFtpServerHandleRETR },
403 { RTFTPSERVER_CMD_SIZE, "SIZE", true, rtFtpServerHandleSIZE },
404 { RTFTPSERVER_CMD_STAT, "STAT", true, rtFtpServerHandleSTAT },
405 { RTFTPSERVER_CMD_STRU, "STRU", true, rtFtpServerHandleSTRU },
406 { RTFTPSERVER_CMD_SYST, "SYST", false, rtFtpServerHandleSYST },
407 { RTFTPSERVER_CMD_TYPE, "TYPE", true, rtFtpServerHandleTYPE },
408 { RTFTPSERVER_CMD_USER, "USER", false, rtFtpServerHandleUSER },
409 { RTFTPSERVER_CMD_LAST, "", false, NULL }
410};
411
412/** RFC-1123 month of the year names. */
413static const char * const g_apszMonths[1+12] =
414{
415 "000", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
416};
417
418/** Feature string which represents all commands we support in addition to RFC 959 (see RFC 2398).
419 * Must match the command table above.
420 *
421 * Don't forget the beginning space (" ") at each feature. */
422#define RTFTPSERVER_FEATURES_STRING \
423 " SIZE\r\n" \
424 " UTF8"
425
426/** Maximum length in characters a FTP server path can have (excluding termination). */
427#define RTFTPSERVER_MAX_PATH RTPATH_MAX
428
429
430/*********************************************************************************************************************************
431* Protocol Functions *
432*********************************************************************************************************************************/
433
434/**
435 * Replies a (three digit) reply code back to the client.
436 *
437 * @returns VBox status code.
438 * @param pClient Client to reply to.
439 * @param enmReply Reply code to send.
440 */
441static int rtFtpServerSendReplyRc(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply)
442{
443 /* Note: If we don't supply any additional text, make sure to include an empty stub, as
444 * some clients expect this as part of their parsing code. */
445 char szReply[32];
446 RTStrPrintf2(szReply, sizeof(szReply), "%RU32 -\r\n", enmReply);
447
448 LogFlowFunc(("Sending reply code %RU32\n", enmReply));
449
450 return RTTcpWrite(pClient->hSocket, szReply, strlen(szReply));
451}
452
453/**
454 * Replies a (three digit) reply code with a custom message back to the client.
455 *
456 * @returns VBox status code.
457 * @param pClient Client to reply to.
458 * @param enmReply Reply code to send.
459 * @param pcszFormat Format string of message to send with the reply code.
460 */
461static int rtFtpServerSendReplyRcEx(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply,
462 const char *pcszFormat, ...)
463{
464 char *pszMsg = NULL;
465
466 va_list args;
467 va_start(args, pcszFormat);
468 char *pszFmt = NULL;
469 const int cch = RTStrAPrintfV(&pszFmt, pcszFormat, args);
470 va_end(args);
471 AssertReturn(cch > 0, VERR_NO_MEMORY);
472
473 int rc = RTStrAPrintf(&pszMsg, "%RU32 -", enmReply);
474 AssertRCReturn(rc, rc);
475
476 /** @todo Support multi-line replies (see 4.2ff). */
477
478 if (pszFmt)
479 {
480 rc = RTStrAAppend(&pszMsg, " ");
481 AssertRCReturn(rc, rc);
482
483 rc = RTStrAAppend(&pszMsg, pszFmt);
484 AssertRCReturn(rc, rc);
485 }
486
487
488 rc = RTStrAAppend(&pszMsg, "\r\n");
489 AssertRCReturn(rc, rc);
490
491 RTStrFree(pszFmt);
492
493 rc = RTTcpWrite(pClient->hSocket, pszMsg, strlen(pszMsg));
494
495 RTStrFree(pszMsg);
496
497 return rc;
498}
499
500/**
501 * Replies a string back to the client.
502 *
503 * @returns VBox status code.
504 * @param pClient Client to reply to.
505 * @param pcszFormat Format to reply.
506 * @param ... Format arguments.
507 */
508static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszFormat, ...)
509{
510 va_list args;
511 va_start(args, pcszFormat);
512 char *psz = NULL;
513 const int cch = RTStrAPrintfV(&psz, pcszFormat, args);
514 va_end(args);
515 AssertReturn(cch > 0, VERR_NO_MEMORY);
516
517 int rc = RTStrAAppend(&psz, "\r\n");
518 AssertRCReturn(rc, rc);
519
520 LogFlowFunc(("Sending reply '%s'\n", psz));
521
522 rc = RTTcpWrite(pClient->hSocket, psz, strlen(psz));
523
524 RTStrFree(psz);
525
526 return rc;
527}
528
529/**
530 * Validates if a given absolute path is valid or not.
531 *
532 * @returns \c true if path is valid, or \c false if not.
533 * @param pcszPath Path to check.
534 * @param fIsAbsolute Whether the path to check is an absolute path or not.
535 */
536static bool rtFtpServerPathIsValid(const char *pcszPath, bool fIsAbsolute)
537{
538 if (!pcszPath)
539 return false;
540
541 bool fIsValid = strlen(pcszPath)
542 && RTStrIsValidEncoding(pcszPath)
543 && RTStrStr(pcszPath, "..") == NULL; /** @todo Very crude for now -- improve this. */
544 if ( fIsValid
545 && fIsAbsolute)
546 {
547 RTFSOBJINFO objInfo;
548 int rc2 = RTPathQueryInfo(pcszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
549 if (RT_SUCCESS(rc2))
550 {
551 fIsValid = RTFS_IS_DIRECTORY(objInfo.Attr.fMode)
552 || RTFS_IS_FILE(objInfo.Attr.fMode);
553
554 /* No symlinks and other stuff not allowed. */
555 }
556 else
557 fIsValid = false;
558 }
559
560 LogFlowFunc(("pcszPath=%s -> %RTbool\n", pcszPath, fIsValid));
561 return fIsValid;
562}
563
564/**
565 * Sets the current working directory for a client.
566 *
567 * @returns VBox status code.
568 * @param pState Client state to set current working directory for.
569 * @param pcszPath Working directory to set.
570 */
571static int rtFtpSetCWD(PRTFTPSERVERCLIENTSTATE pState, const char *pcszPath)
572{
573 RTStrFree(pState->pszCWD);
574
575 if (!rtFtpServerPathIsValid(pcszPath, false /* fIsAbsolute */))
576 return VERR_INVALID_PARAMETER;
577
578 pState->pszCWD = RTStrDup(pcszPath);
579
580 LogFlowFunc(("Current CWD is now '%s'\n", pState->pszCWD));
581
582 int rc = pState->pszCWD ? VINF_SUCCESS : VERR_NO_MEMORY;
583 AssertRC(rc);
584 return rc;
585}
586
587/**
588 * Looks up an user account.
589 *
590 * @returns VBox status code, or VERR_NOT_FOUND if user has not been found.
591 * @param pClient Client to look up user for.
592 * @param pcszUser User name to look up.
593 */
594static int rtFtpServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pcszUser)
595{
596 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pcszUser);
597}
598
599/**
600 * Handles the actual client authentication.
601 *
602 * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed.
603 * @param pClient Client to authenticate.
604 * @param pcszUser User name to authenticate with.
605 * @param pcszPassword Password to authenticate with.
606 */
607static int rtFtpServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pcszUser, const char *pcszPassword)
608{
609 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pcszUser, pcszPassword);
610}
611
612/**
613 * Converts a RTFSOBJINFO struct to a string.
614 *
615 * @returns VBox status code.
616 * @param pObjInfo RTFSOBJINFO object to convert.
617 * @param pszFsObjInfo Where to store the output string.
618 * @param cbFsObjInfo Size of the output string in bytes.
619 */
620static int rtFtpServerFsObjInfoToStr(PRTFSOBJINFO pObjInfo, char *pszFsObjInfo, size_t cbFsObjInfo)
621{
622 RTFMODE fMode = pObjInfo->Attr.fMode;
623 char chFileType;
624 switch (fMode & RTFS_TYPE_MASK)
625 {
626 case RTFS_TYPE_FIFO: chFileType = 'f'; break;
627 case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
628 case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
629 case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
630 case RTFS_TYPE_FILE: chFileType = '-'; break;
631 case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
632 case RTFS_TYPE_SOCKET: chFileType = 's'; break;
633 case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
634 default: chFileType = '?'; break;
635 }
636
637 char szTimeBirth[RTTIME_STR_LEN];
638 char szTimeChange[RTTIME_STR_LEN];
639 char szTimeModification[RTTIME_STR_LEN];
640 char szTimeAccess[RTTIME_STR_LEN];
641
642#define INFO_TO_STR(a_Format, ...) \
643 do \
644 { \
645 const ssize_t cchSize = RTStrPrintf2(szTemp, sizeof(szTemp), a_Format, __VA_ARGS__); \
646 AssertReturn(cchSize > 0, VERR_BUFFER_OVERFLOW); \
647 const int rc2 = RTStrCat(pszFsObjInfo, cbFsObjInfo, szTemp); \
648 AssertRCReturn(rc2, rc2); \
649 } while (0);
650
651 char szTemp[128];
652
653 INFO_TO_STR("%c", chFileType);
654 INFO_TO_STR("%c%c%c",
655 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
656 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
657 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
658 INFO_TO_STR("%c%c%c",
659 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
660 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
661 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
662 INFO_TO_STR("%c%c%c",
663 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
664 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
665 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
666
667 INFO_TO_STR( " %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
668 fMode & RTFS_DOS_READONLY ? 'R' : '-',
669 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
670 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
671 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
672 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
673 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
674 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
675 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
676 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
677 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
678 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
679 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
680 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
681 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
682
683 INFO_TO_STR( " %d %4d %4d %10lld %10lld",
684 pObjInfo->Attr.u.Unix.cHardlinks,
685 pObjInfo->Attr.u.Unix.uid,
686 pObjInfo->Attr.u.Unix.gid,
687 pObjInfo->cbObject,
688 pObjInfo->cbAllocated);
689
690 INFO_TO_STR( " %s %s %s %s",
691 RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
692 RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
693 RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
694 RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
695
696#undef INFO_TO_STR
697
698 return VINF_SUCCESS;
699}
700
701/**
702 * Parses a string which consists of an IPv4 (ww,xx,yy,zz) and a port number (hi,lo), all separated by comma delimiters.
703 * See RFC 959, 4.1.2.
704 *
705 * @returns VBox status code.
706 * @param pcszStr String to parse.
707 * @param pAddr Where to store the IPv4 address on success.
708 * @param puPort Where to store the port number on success.
709 */
710static int rtFtpParseHostAndPort(const char *pcszStr, PRTNETADDRIPV4 pAddr, uint16_t *puPort)
711{
712 AssertPtrReturn(pcszStr, VERR_INVALID_POINTER);
713 AssertPtrReturn(pAddr, VERR_INVALID_POINTER);
714 AssertPtrReturn(puPort, VERR_INVALID_POINTER);
715
716 char *pszNext;
717 int rc;
718
719 /* Parse IP (v4). */
720 /** @todo I don't think IPv6 ever will be a thing here, or will it? */
721 rc = RTStrToUInt8Ex(pcszStr, &pszNext, 10, &pAddr->au8[0]);
722 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
723 return VERR_INVALID_PARAMETER;
724 if (*pszNext++ != ',')
725 return VERR_INVALID_PARAMETER;
726
727 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]);
728 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
729 return VERR_INVALID_PARAMETER;
730 if (*pszNext++ != ',')
731 return VERR_INVALID_PARAMETER;
732
733 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]);
734 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
735 return VERR_INVALID_PARAMETER;
736 if (*pszNext++ != ',')
737 return VERR_INVALID_PARAMETER;
738
739 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]);
740 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
741 return VERR_INVALID_PARAMETER;
742 if (*pszNext++ != ',')
743 return VERR_INVALID_PARAMETER;
744
745 /* Parse port. */
746 uint8_t uPortHi;
747 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortHi);
748 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
749 return VERR_INVALID_PARAMETER;
750 if (*pszNext++ != ',')
751 return VERR_INVALID_PARAMETER;
752 uint8_t uPortLo;
753 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortLo);
754 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
755 return VERR_INVALID_PARAMETER;
756
757 *puPort = RT_MAKE_U16(uPortLo, uPortHi);
758
759 return rc;
760}
761
762/**
763 * Duplicates a command argument vector.
764 *
765 * @returns Duplicated argument vector or NULL if failed or no arguments given. Needs to be free'd with rtFtpCmdArgsFree().
766 * @param cArgs Number of arguments in argument vector.
767 * @param apcszArgs Pointer to argument vector to duplicate.
768 */
769static char** rtFtpCmdArgsDup(uint8_t cArgs, const char * const *apcszArgs)
770{
771 if (!cArgs)
772 return NULL;
773
774 char **apcszArgsDup = (char **)RTMemAlloc(cArgs * sizeof(char *));
775 if (!apcszArgsDup)
776 {
777 AssertFailed();
778 return NULL;
779 }
780
781 int rc2 = VINF_SUCCESS;
782
783 uint8_t i;
784 for (i = 0; i < cArgs; i++)
785 {
786 apcszArgsDup[i] = RTStrDup(apcszArgs[i]);
787 if (!apcszArgsDup[i])
788 rc2 = VERR_NO_MEMORY;
789 }
790
791 if (RT_FAILURE(rc2))
792 {
793 while (i--)
794 RTStrFree(apcszArgsDup[i]);
795
796 RTMemFree(apcszArgsDup);
797 return NULL;
798 }
799
800 return apcszArgsDup;
801}
802
803/**
804 * Frees a command argument vector.
805 *
806 * @param cArgs Number of arguments in argument vector.
807 * @param papcszArgs Pointer to argument vector to free.
808 */
809static void rtFtpCmdArgsFree(uint8_t cArgs, char **papcszArgs)
810{
811 while (cArgs--)
812 RTStrFree(papcszArgs[cArgs]);
813
814 RTMemFree(papcszArgs);
815}
816
817/**
818 * Opens a data connection to the client.
819 *
820 * @returns VBox status code.
821 * @param pDataConn Data connection to open.
822 * @param pAddr Address for the data connection.
823 * @param uPort Port for the data connection.
824 */
825static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort)
826{
827 LogFlowFuncEnter();
828
829 /** @todo Implement IPv6 handling here. */
830 char szAddress[32];
831 const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8",
832 pAddr->au8[0], pAddr->au8[1], pAddr->au8[2], pAddr->au8[3]);
833 AssertReturn(cchAdddress > 0, VERR_NO_MEMORY);
834
835 int rc = VINF_SUCCESS; /* Shut up MSVC. */
836
837 /* Try a bit harder if the data connection is not ready (yet). */
838 for (int i = 0; i < 10; i++)
839 {
840 rc = RTTcpClientConnect(szAddress, uPort, &pDataConn->hSocket);
841 if (RT_SUCCESS(rc))
842 break;
843 RTThreadSleep(100);
844 }
845
846 LogFlowFuncLeaveRC(rc);
847 return rc;
848}
849
850/**
851 * Closes a data connection to the client.
852 *
853 * @returns VBox status code.
854 * @param pDataConn Data connection to close.
855 */
856static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn)
857{
858 int rc = VINF_SUCCESS;
859
860 if (pDataConn->hSocket != NIL_RTSOCKET)
861 {
862 LogFlowFuncEnter();
863
864 rtFtpServerDataConnFlush(pDataConn);
865
866 rc = RTTcpClientClose(pDataConn->hSocket);
867 pDataConn->hSocket = NIL_RTSOCKET;
868 }
869
870 LogFlowFuncLeaveRC(rc);
871 return rc;
872}
873
874/**
875 * Writes data to the data connection.
876 *
877 * @returns VBox status code.
878 * @param pDataConn Data connection to write to.
879 * @param pvData Data to write.
880 * @param cbData Size (in bytes) of data to write.
881 * @param pcbWritten How many bytes were written. Optional.
882 */
883static int rtFtpServerDataConnWrite(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData, size_t *pcbWritten)
884{
885 int rc = RTTcpWrite(pDataConn->hSocket, pvData, cbData);
886 if (RT_SUCCESS(rc))
887 {
888 if (pcbWritten)
889 *pcbWritten = cbData;
890 }
891
892 return rc;
893}
894
895/**
896 * Flushes a data connection.
897 *
898 * @returns VBox status code.
899 * @param pDataConn Data connection to flush.
900 */
901static int rtFtpServerDataConnFlush(PRTFTPSERVERDATACONN pDataConn)
902{
903 int rc = VINF_SUCCESS;
904
905 size_t cbUsed = RTCircBufUsed(pDataConn->pCircBuf);
906 while (cbUsed)
907 {
908 void *pvBlock;
909 size_t cbBlock;
910 RTCircBufAcquireReadBlock(pDataConn->pCircBuf, cbUsed, &pvBlock, &cbBlock);
911 if (cbBlock)
912 {
913 size_t cbWritten = 0;
914 rc = rtFtpServerDataConnWrite(pDataConn, pvBlock, cbBlock, &cbWritten);
915 if (RT_SUCCESS(rc))
916 {
917 AssertBreak(cbUsed >= cbWritten);
918 cbUsed -= cbWritten;
919 }
920
921 RTCircBufReleaseReadBlock(pDataConn->pCircBuf, cbWritten);
922
923 if (RT_FAILURE(rc))
924 break;
925 }
926 }
927
928 return rc;
929}
930
931/**
932 * Checks if flushing a data connection is necessary, and if so, flush it.
933 *
934 * @returns VBox status code.
935 * @param pDataConn Data connection to check / do flushing for.
936 */
937static int rtFtpServerDataCheckFlush(PRTFTPSERVERDATACONN pDataConn)
938{
939 int rc = VINF_SUCCESS;
940
941 size_t cbUsed = RTCircBufUsed(pDataConn->pCircBuf);
942 if (cbUsed >= _4K) /** @todo Make this more dynamic. */
943 {
944 rc = rtFtpServerDataConnFlush(pDataConn);
945 }
946
947 return rc;
948}
949
950/**
951 * Adds new data for a data connection to be sent.
952 *
953 * @returns VBox status code.
954 * @param pDataConn Data connection to add new data to.
955 * @param pvData Pointer to data to add.
956 * @param cbData Size (in bytes) of data to add.
957 */
958static int rtFtpServerDataConnAddData(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData)
959{
960 AssertReturn(cbData <= RTCircBufFree(pDataConn->pCircBuf), VERR_BUFFER_OVERFLOW);
961
962 int rc = VINF_SUCCESS;
963
964 size_t cbToWrite = cbData;
965 do
966 {
967 void *pvBlock;
968 size_t cbBlock;
969 RTCircBufAcquireWriteBlock(pDataConn->pCircBuf, cbToWrite, &pvBlock, &cbBlock);
970 if (cbBlock)
971 {
972 AssertBreak(cbData >= cbBlock);
973 memcpy(pvBlock, pvData, cbBlock);
974
975 AssertBreak(cbToWrite >= cbBlock);
976 cbToWrite -= cbBlock;
977
978 RTCircBufReleaseWriteBlock(pDataConn->pCircBuf, cbBlock);
979 }
980
981 } while (cbToWrite);
982
983 if (RT_SUCCESS(rc))
984 rc = rtFtpServerDataCheckFlush(pDataConn);
985
986 return rc;
987}
988
989/**
990 * Does a printf-style write on a data connection.
991 *
992 * @returns VBox status code.
993 * @param pDataConn Data connection to write to.
994 * @param pcszFormat Format string to send. No (terminal) termination added.
995 */
996static int rtFtpServerDataConnPrintf(PRTFTPSERVERDATACONN pDataConn, const char *pcszFormat, ...)
997{
998 va_list args;
999 va_start(args, pcszFormat);
1000 char *pszFmt = NULL;
1001 const int cch = RTStrAPrintfV(&pszFmt, pcszFormat, args);
1002 va_end(args);
1003 AssertReturn(cch > 0, VERR_NO_MEMORY);
1004
1005 char *pszMsg = NULL;
1006 int rc = RTStrAAppend(&pszMsg, pszFmt);
1007 AssertRCReturn(rc, rc);
1008
1009 RTStrFree(pszFmt);
1010
1011 rc = rtFtpServerDataConnAddData(pDataConn, pszMsg, strlen(pszMsg));
1012
1013 RTStrFree(pszMsg);
1014
1015 return rc;
1016}
1017
1018/**
1019 * Data connection thread for writing (sending) a file to the client.
1020 *
1021 * @returns VBox status code.
1022 * @param ThreadSelf Thread handle. Unused at the moment.
1023 * @param pvUser Pointer to user-provided data. Of type PRTFTPSERVERCLIENT.
1024 */
1025static DECLCALLBACK(int) rtFtpServerDataConnFileWriteThread(RTTHREAD ThreadSelf, void *pvUser)
1026{
1027 RT_NOREF(ThreadSelf);
1028
1029 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
1030 AssertPtr(pClient);
1031
1032 PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn;
1033 AssertPtr(pDataConn);
1034
1035 LogFlowFuncEnter();
1036
1037 uint32_t cbBuf = _64K; /** @todo Improve this. */
1038 void *pvBuf = RTMemAlloc(cbBuf);
1039 if (!pvBuf)
1040 return VERR_NO_MEMORY;
1041
1042 int rc;
1043
1044 /* Set start indicator. */
1045 pDataConn->fStarted = true;
1046
1047 RTThreadUserSignal(RTThreadSelf());
1048
1049 AssertPtr(pDataConn->papszArgs);
1050 const char *pcszFile = pDataConn->papszArgs[0];
1051 AssertPtr(pcszFile);
1052
1053 void *pvHandle = NULL; /* Opaque handle known to the actual implementation. */
1054
1055 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileOpen, pcszFile,
1056 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pvHandle);
1057 if (RT_SUCCESS(rc))
1058 {
1059 LogFlowFunc(("Transfer started\n"));
1060
1061 do
1062 {
1063 size_t cbRead = 0;
1064 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead);
1065 if ( RT_SUCCESS(rc)
1066 && cbRead)
1067 {
1068 rc = rtFtpServerDataConnWrite(pDataConn, pvBuf, cbRead, NULL /* pcbWritten */);
1069 }
1070
1071 if ( !cbRead
1072 || ASMAtomicReadBool(&pDataConn->fStop))
1073 {
1074 break;
1075 }
1076 }
1077 while (RT_SUCCESS(rc));
1078
1079 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle);
1080
1081 LogFlowFunc(("Transfer done\n"));
1082 }
1083
1084 RTMemFree(pvBuf);
1085 pvBuf = NULL;
1086
1087 pDataConn->fStopped = true;
1088 pDataConn->rc = rc;
1089
1090 LogFlowFuncLeaveRC(rc);
1091 return rc;
1092}
1093
1094/**
1095 * Creates a data connection.
1096 *
1097 * @returns VBox status code.
1098 * @param pClient Client to create data connection for.
1099 * @param ppDataConn Where to return the (allocated) data connection.
1100 */
1101static int rtFtpServerDataConnCreate(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN *ppDataConn)
1102{
1103 if (pClient->pDataConn)
1104 return VERR_FTP_DATA_CONN_LIMIT_REACHED;
1105
1106 PRTFTPSERVERDATACONN pDataConn = (PRTFTPSERVERDATACONN)RTMemAllocZ(sizeof(RTFTPSERVERDATACONN));
1107 if (!pDataConn)
1108 return VERR_NO_MEMORY;
1109
1110 rtFtpServerDataConnReset(pDataConn);
1111
1112 pDataConn->pClient = pClient;
1113
1114 /* Use the last configured addr + port. */
1115 pDataConn->Addr = pClient->DataConnAddr;
1116 pDataConn->uPort = pClient->uDataConnPort;
1117
1118 int rc = RTCircBufCreate(&pDataConn->pCircBuf, _16K); /** @todo Some random value; improve. */
1119 if (RT_SUCCESS(rc))
1120 {
1121 *ppDataConn = pDataConn;
1122 }
1123
1124 LogFlowFuncLeaveRC(VINF_SUCCESS);
1125 return rc;
1126}
1127
1128/**
1129 * Starts a data connection.
1130 *
1131 * @returns VBox status code.
1132 * @param pDataConn Data connection to start.
1133 * @param pfnThread Thread function for the data connection to use.
1134 * @param cArgs Number of arguments.
1135 * @param apcszArgs Array of arguments.
1136 */
1137static int rtFtpServerDataConnStart(PRTFTPSERVERDATACONN pDataConn, PFNRTTHREAD pfnThread,
1138 uint8_t cArgs, const char * const *apcszArgs)
1139{
1140 AssertPtrReturn(pDataConn, VERR_INVALID_POINTER);
1141 AssertPtrReturn(pfnThread, VERR_INVALID_POINTER);
1142
1143 AssertReturn(!pDataConn->fStarted, VERR_WRONG_ORDER);
1144 AssertReturn(!pDataConn->fStop, VERR_WRONG_ORDER);
1145 AssertReturn(!pDataConn->fStopped, VERR_WRONG_ORDER);
1146
1147 int rc = VINF_SUCCESS;
1148
1149 if (cArgs)
1150 {
1151 pDataConn->papszArgs = rtFtpCmdArgsDup(cArgs, apcszArgs);
1152 if (!pDataConn->papszArgs)
1153 rc = VERR_NO_MEMORY;
1154 }
1155
1156 if (RT_SUCCESS(rc))
1157 {
1158 pDataConn->cArgs = cArgs;
1159
1160 rc = rtFtpServerDataConnOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort);
1161 if (RT_SUCCESS(rc))
1162 {
1163 rc = RTThreadCreate(&pDataConn->hThread, pfnThread,
1164 pDataConn->pClient, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
1165 "ftpdata");
1166 if (RT_SUCCESS(rc))
1167 {
1168 int rc2 = RTThreadUserWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */);
1169 AssertRC(rc2);
1170
1171 if (!pDataConn->fStarted) /* Did the thread indicate that it started correctly? */
1172 rc = VERR_FTP_DATA_CONN_INIT_FAILED;
1173 }
1174
1175 if (RT_FAILURE(rc))
1176 rtFtpServerDataConnClose(pDataConn);
1177 }
1178 }
1179
1180 if (RT_FAILURE(rc))
1181 {
1182 rtFtpCmdArgsFree(pDataConn->cArgs, pDataConn->papszArgs);
1183
1184 pDataConn->cArgs = 0;
1185 pDataConn->papszArgs = NULL;
1186 }
1187
1188 LogFlowFuncLeaveRC(rc);
1189 return rc;
1190}
1191
1192/**
1193 * Stops a data connection.
1194 *
1195 * @returns VBox status code.
1196 * @param pDataConn Data connection to stop.
1197 */
1198static int rtFtpServerDataConnStop(PRTFTPSERVERDATACONN pDataConn)
1199{
1200 if (!pDataConn)
1201 return VINF_SUCCESS;
1202
1203 LogFlowFuncEnter();
1204
1205 int rc = VINF_SUCCESS;
1206
1207 if (pDataConn->hThread != NIL_RTTHREAD)
1208 {
1209 /* Set stop indicator. */
1210 pDataConn->fStop = true;
1211
1212 int rcThread = VERR_WRONG_ORDER;
1213 rc = RTThreadWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */, &rcThread);
1214 }
1215
1216 if (RT_SUCCESS(rc))
1217 rtFtpServerDataConnClose(pDataConn);
1218
1219 LogFlowFuncLeaveRC(rc);
1220 return rc;
1221}
1222
1223/**
1224 * Destroys a data connection.
1225 *
1226 * @returns VBox status code.
1227 * @param pDataConn Data connection to destroy. The pointer is not valid anymore after successful return.
1228 */
1229static void rtFtpServerDataConnDestroy(PRTFTPSERVERDATACONN pDataConn)
1230{
1231 if (!pDataConn)
1232 return;
1233
1234 LogFlowFuncEnter();
1235
1236 rtFtpServerDataConnClose(pDataConn);
1237 rtFtpCmdArgsFree(pDataConn->cArgs, pDataConn->papszArgs);
1238
1239 RTCircBufDestroy(pDataConn->pCircBuf);
1240
1241 RTMemFree(pDataConn);
1242 pDataConn = NULL;
1243
1244 LogFlowFuncLeave();
1245 return;
1246}
1247
1248/**
1249 * Resets a data connection structure.
1250 *
1251 * @returns VBox status code.
1252 * @param pDataConn Data connection structure to reset.
1253 */
1254static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn)
1255{
1256 LogFlowFuncEnter();
1257
1258 pDataConn->hSocket = NIL_RTSOCKET;
1259 pDataConn->uPort = 20; /* Default port to use. */
1260 pDataConn->hThread = NIL_RTTHREAD;
1261 pDataConn->fStarted = false;
1262 pDataConn->fStop = false;
1263 pDataConn->fStopped = false;
1264 pDataConn->rc = VERR_IPE_UNINITIALIZED_STATUS;
1265}
1266
1267
1268/*********************************************************************************************************************************
1269* Command Protocol Handlers *
1270*********************************************************************************************************************************/
1271
1272static int rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1273{
1274 RT_NOREF(cArgs, apcszArgs);
1275
1276 int rc = rtFtpServerDataConnClose(pClient->pDataConn);
1277 if (RT_SUCCESS(rc))
1278 {
1279 rtFtpServerDataConnDestroy(pClient->pDataConn);
1280 pClient->pDataConn = NULL;
1281
1282 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1283 }
1284
1285 return rc;
1286}
1287
1288static int rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1289{
1290 RT_NOREF(cArgs, apcszArgs);
1291
1292 int rc;
1293
1294 RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp);
1295
1296 if (RT_SUCCESS(rc))
1297 {
1298 const size_t cbPath = sizeof(char) * RTFTPSERVER_MAX_PATH;
1299 char *pszPath = RTStrAlloc(cbPath);
1300 if (pszPath)
1301 {
1302 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, pszPath, cbPath);
1303
1304 if (RT_SUCCESS(rc))
1305 rc = rtFtpSetCWD(&pClient->State, pszPath);
1306
1307 RTStrFree(pszPath);
1308
1309 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1310 }
1311 else
1312 rc = VERR_NO_MEMORY;
1313 }
1314
1315 if (RT_FAILURE(rc))
1316 {
1317 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1318 AssertRC(rc2);
1319 }
1320
1321 return rc;
1322}
1323
1324static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1325{
1326 if (cArgs != 1)
1327 return VERR_INVALID_PARAMETER;
1328
1329 int rc;
1330
1331 const char *pcszPath = apcszArgs[0];
1332
1333 if (!rtFtpServerPathIsValid(pcszPath, false /* fIsAbsolute */))
1334 return VERR_INVALID_PARAMETER;
1335
1336 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, pcszPath);
1337
1338 if (RT_SUCCESS(rc))
1339 rc = rtFtpSetCWD(&pClient->State, pcszPath);
1340
1341 return rtFtpServerSendReplyRc(pClient,
1342 RT_SUCCESS(rc)
1343 ? RTFTPSERVER_REPLY_OKAY : RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1344}
1345
1346static int rtFtpServerHandleFEAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1347{
1348 RT_NOREF(cArgs, apcszArgs);
1349
1350 int rc = rtFtpServerSendReplyStr(pClient, "211-BEGIN Features:");
1351 if (RT_SUCCESS(rc))
1352 {
1353 rc = rtFtpServerSendReplyStr(pClient, RTFTPSERVER_FEATURES_STRING);
1354 if (RT_SUCCESS(rc))
1355 rc = rtFtpServerSendReplyStr(pClient, "211 END Features");
1356 }
1357
1358 return rc;
1359}
1360
1361/**
1362 * Formats the given user ID according to the specified options.
1363 *
1364 * @returns pszDst
1365 * @param uid The UID to format.
1366 * @param pszOwner The owner returned by the FS.
1367 * @param pszDst The output buffer.
1368 * @param cbDst The output buffer size.
1369 */
1370static const char *rtFtpServerDecimalFormatOwner(RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst)
1371{
1372 if (pszOwner)
1373 {
1374 RTStrCopy(pszDst, cbDst, pszOwner);
1375 return pszDst;
1376 }
1377 if (uid == NIL_RTUID)
1378 return "<Nil>";
1379
1380 RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0);
1381 return pszDst;
1382}
1383
1384/**
1385 * Formats the given group ID according to the specified options.
1386 *
1387 * @returns pszDst
1388 * @param gid The GID to format.
1389 * @param pszGroup The group returned by the FS.
1390 * @param pszDst The output buffer.
1391 * @param cbDst The output buffer size.
1392 */
1393static const char *rtFtpServerDecimalFormatGroup(RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst)
1394{
1395 if (pszGroup)
1396 {
1397 RTStrCopy(pszDst, cbDst, pszGroup);
1398 return pszDst;
1399 }
1400 if (gid == NIL_RTGID)
1401 return "<Nil>";
1402
1403 RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0);
1404 return pszDst;
1405}
1406
1407/**
1408 * Format file size.
1409 */
1410static const char *rtFtpServerFormatSize(uint64_t cb, char *pszDst, size_t cbDst)
1411{
1412 RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
1413 return pszDst;
1414}
1415
1416/**
1417 * Formats the given timestamp according to (non-standardized) FTP LIST command.
1418 *
1419 * @returns pszDst
1420 * @param pTimestamp The timestamp to format.
1421 * @param pszDst The output buffer.
1422 * @param cbDst The output buffer size.
1423 */
1424static const char *rtFtpServerFormatTimestamp(PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst)
1425{
1426 RTTIME Time;
1427 RTTimeExplode(&Time, pTimestamp);
1428
1429 /* Calc the UTC offset part. */
1430 int32_t offUtc = Time.offUTC;
1431 Assert(offUtc <= 840 && offUtc >= -840);
1432 char chSign;
1433 if (offUtc >= 0)
1434 chSign = '+';
1435 else
1436 {
1437 chSign = '-';
1438 offUtc = -offUtc;
1439 }
1440 uint32_t offUtcHour = (uint32_t)offUtc / 60;
1441 uint32_t offUtcMinute = (uint32_t)offUtc % 60;
1442
1443 /** @todo Cache this. */
1444 RTTIMESPEC TimeSpecNow;
1445 RTTimeNow(&TimeSpecNow);
1446 RTTIME TimeNow;
1447 RTTimeExplode(&TimeNow, &TimeSpecNow);
1448
1449 /* Only include the year if it's not the same year as today. */
1450 if (TimeNow.i32Year != Time.i32Year)
1451 {
1452 RTStrPrintf(pszDst, cbDst, "%s %02RU8 %5RU32",
1453 g_apszMonths[Time.u8Month], Time.u8MonthDay, Time.i32Year);
1454 }
1455 else /* ... otherwise include the (rough) time (as GMT). */
1456 {
1457 RTStrPrintf(pszDst, cbDst, "%s %02RU8 %02RU32:%02RU32",
1458 g_apszMonths[Time.u8Month], Time.u8MonthDay, offUtcHour, offUtcMinute);
1459 }
1460
1461 return pszDst;
1462}
1463
1464/**
1465 * Format name, i.e. escape, hide, quote stuff.
1466 */
1467static const char *rtFtpServerFormatName(const char *pszName, char *pszDst, size_t cbDst)
1468{
1469 /** @todo implement name formatting. */
1470 RT_NOREF(pszDst, cbDst);
1471 return pszName;
1472}
1473
1474/**
1475 * Figures out the length for a 32-bit number when formatted as decimal.
1476 * @returns Number of digits.
1477 * @param uValue The number.
1478 */
1479DECLINLINE(size_t) rtFtpServerDecimalFormatLengthU32(uint32_t uValue)
1480{
1481 if (uValue < 10)
1482 return 1;
1483 if (uValue < 100)
1484 return 2;
1485 if (uValue < 1000)
1486 return 3;
1487 if (uValue < 10000)
1488 return 4;
1489 if (uValue < 100000)
1490 return 5;
1491 if (uValue < 1000000)
1492 return 6;
1493 if (uValue < 10000000)
1494 return 7;
1495 if (uValue < 100000000)
1496 return 8;
1497 if (uValue < 1000000000)
1498 return 9;
1499 return 10;
1500}
1501
1502/**
1503 * Allocates a new directory collection.
1504 *
1505 * @returns The collection allocated.
1506 */
1507static PRTFTPDIRCOLLECTION rtFtpServerDataConnDirCollAlloc(void)
1508{
1509 return (PRTFTPDIRCOLLECTION)RTMemAllocZ(sizeof(RTFTPDIRCOLLECTION));
1510}
1511
1512/**
1513 * Frees a directory collection and its entries.
1514 *
1515 * @param pCollection The collection to free.
1516 */
1517static void rtFtpServerDataConnDirCollFree(PRTFTPDIRCOLLECTION pCollection)
1518{
1519 PPRTFTPDIRENTRY papEntries = pCollection->papEntries;
1520 size_t j = pCollection->cEntries;
1521 while (j-- > 0)
1522 {
1523 RTMemFree(papEntries[j]);
1524 papEntries[j] = NULL;
1525 }
1526 RTMemFree(papEntries);
1527 pCollection->papEntries = NULL;
1528 pCollection->cEntries = 0;
1529 pCollection->cEntriesAllocated = 0;
1530 RTMemFree(pCollection);
1531}
1532
1533/**
1534 * Adds one entry to a collection.
1535 *
1536 * @returns VBox status code.
1537 * @param pCollection The collection to add entry to.
1538 * @param pszEntry The entry name.
1539 * @param pInfo The entry info.
1540 * @param pszOwner The owner name if available, otherwise NULL.
1541 * @param pszGroup The group anme if available, otherwise NULL.
1542 * @param pszTarget The symbolic link target if applicable and
1543 * available, otherwise NULL.
1544 */
1545static int rtFtpServerDataConnDirCollAddEntry(PRTFTPDIRCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo,
1546 const char *pszOwner, const char *pszGroup, const char *pszTarget)
1547{
1548 /* Filter out entries we don't want to report to the client, even if they were reported by the actual implementation. */
1549 if ( !RTStrCmp(pszEntry, ".")
1550 || !RTStrCmp(pszEntry, ".."))
1551 {
1552 return VINF_SUCCESS;
1553 }
1554
1555 /* Anything else besides files and directores is not allowed; just don't show them at all for the moment. */
1556 switch (pInfo->Attr.fMode & RTFS_TYPE_MASK)
1557 {
1558 case RTFS_TYPE_DIRECTORY:
1559 RT_FALL_THROUGH();
1560 case RTFS_TYPE_FILE:
1561 break;
1562
1563 default:
1564 return VINF_SUCCESS;
1565 }
1566
1567 /* Make sure there is space in the collection for the new entry. */
1568 if (pCollection->cEntries >= pCollection->cEntriesAllocated)
1569 {
1570 size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16;
1571 void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0]));
1572 if (!pvNew)
1573 return VERR_NO_MEMORY;
1574 pCollection->papEntries = (PPRTFTPDIRENTRY)pvNew;
1575 pCollection->cEntriesAllocated = cNew;
1576 }
1577
1578 /* Create and insert a new entry. */
1579 size_t const cchEntry = strlen(pszEntry);
1580 size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0;
1581 size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0;
1582 size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0;
1583 size_t const cbEntry = RT_UOFFSETOF_DYN(RTFTPDIRENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]);
1584 PRTFTPDIRENTRY pEntry = (PRTFTPDIRENTRY)RTMemAlloc(cbEntry);
1585 if (pEntry)
1586 {
1587 pEntry->Info = *pInfo;
1588 pEntry->pszTarget = NULL; /** @todo symbolic links. */
1589 pEntry->pszOwner = NULL;
1590 pEntry->pszGroup = NULL;
1591 pEntry->cchName = cchEntry;
1592 memcpy(pEntry->szName, pszEntry, cchEntry);
1593 pEntry->szName[cchEntry] = '\0';
1594
1595 char *psz = &pEntry->szName[cchEntry + 1];
1596 if (pszTarget)
1597 {
1598 pEntry->pszTarget = psz;
1599 memcpy(psz, pszTarget, cbTarget);
1600 psz += cbTarget;
1601 }
1602 if (pszOwner)
1603 {
1604 pEntry->pszOwner = psz;
1605 memcpy(psz, pszOwner, cbOwner);
1606 psz += cbOwner;
1607 }
1608 if (pszGroup)
1609 {
1610 pEntry->pszGroup = psz;
1611 memcpy(psz, pszGroup, cbGroup);
1612 }
1613
1614 pCollection->papEntries[pCollection->cEntries++] = pEntry;
1615 pCollection->cbTotalAllocated += pEntry->Info.cbAllocated;
1616 pCollection->cbTotalFiles += pEntry->Info.cbObject;
1617 return VINF_SUCCESS;
1618 }
1619 return VERR_NO_MEMORY;
1620}
1621
1622/** @callback_method_impl{FNRTSORTCMP, Name} */
1623static DECLCALLBACK(int) rtFtpServerCollEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser)
1624{
1625 RT_NOREF(pvUser);
1626 PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1;
1627 PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2;
1628 return RTStrCmp(pEntry1->szName, pEntry2->szName);
1629}
1630
1631/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */
1632static DECLCALLBACK(int) rtFtpServerCollEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser)
1633{
1634 RT_NOREF(pvUser);
1635 PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1;
1636 PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2;
1637 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
1638 if (!iDiff)
1639 iDiff = rtFtpServerCollEntryCmpName(pEntry1, pEntry2, pvUser);
1640 return iDiff;
1641}
1642
1643/**
1644 * Sorts a given directory collection according to the FTP server's LIST style.
1645 *
1646 * @param pCollection Collection to sort.
1647 */
1648static void rtFtpServerCollSort(PRTFTPDIRCOLLECTION pCollection)
1649{
1650 PFNRTSORTCMP pfnCmp = rtFtpServerCollEntryCmpDirFirstName;
1651 if (pfnCmp)
1652 RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL);
1653}
1654
1655/**
1656 * Writes a directory collection to a specific data connection.
1657 *
1658 * @returns VBox status code.
1659 * @param pDataConn Data connection to write directory collection to.
1660 * @param pCollection Collection to write.
1661 * @param pszTmp Temporary buffer used for writing.
1662 * @param cbTmp Size (in bytes) of temporary buffer used for writing.
1663 */
1664static int rtFtpServerDataConnDirCollWrite(PRTFTPSERVERDATACONN pDataConn, PRTFTPDIRCOLLECTION pCollection,
1665 char *pszTmp, size_t cbTmp)
1666{
1667 size_t cchSizeCol = 4;
1668 size_t cchLinkCol = 1;
1669 size_t cchUidCol = 1;
1670 size_t cchGidCol = 1;
1671
1672 size_t i = pCollection->cEntries;
1673 while (i-- > 0)
1674 {
1675 PRTFTPDIRENTRY pEntry = pCollection->papEntries[i];
1676
1677 rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp);
1678 size_t cchTmp = strlen(pszTmp);
1679 if (cchTmp > cchSizeCol)
1680 cchSizeCol = cchTmp;
1681
1682 cchTmp = rtFtpServerDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1;
1683 if (cchTmp > cchLinkCol)
1684 cchLinkCol = cchTmp;
1685
1686 rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp);
1687 cchTmp = strlen(pszTmp);
1688 if (cchTmp > cchUidCol)
1689 cchUidCol = cchTmp;
1690
1691 rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp);
1692 cchTmp = strlen(pszTmp);
1693 if (cchTmp > cchGidCol)
1694 cchGidCol = cchTmp;
1695 }
1696
1697 size_t offTime = RT_UOFFSETOF(RTFTPDIRENTRY, Info.ModificationTime);
1698
1699 /*
1700 * Display the entries.
1701 */
1702 for (i = 0; i < pCollection->cEntries; i++)
1703 {
1704 PRTFTPDIRENTRY pEntry = pCollection->papEntries[i];
1705
1706 RTFMODE fMode = pEntry->Info.Attr.fMode;
1707 switch (fMode & RTFS_TYPE_MASK)
1708 {
1709 case RTFS_TYPE_FIFO: rtFtpServerDataConnPrintf(pDataConn, "f"); break;
1710 case RTFS_TYPE_DEV_CHAR: rtFtpServerDataConnPrintf(pDataConn, "c"); break;
1711 case RTFS_TYPE_DIRECTORY: rtFtpServerDataConnPrintf(pDataConn, "d"); break;
1712 case RTFS_TYPE_DEV_BLOCK: rtFtpServerDataConnPrintf(pDataConn, "b"); break;
1713 case RTFS_TYPE_FILE: rtFtpServerDataConnPrintf(pDataConn, "-"); break;
1714 case RTFS_TYPE_SYMLINK: rtFtpServerDataConnPrintf(pDataConn, "l"); break;
1715 case RTFS_TYPE_SOCKET: rtFtpServerDataConnPrintf(pDataConn, "s"); break;
1716 case RTFS_TYPE_WHITEOUT: rtFtpServerDataConnPrintf(pDataConn, "w"); break;
1717 default: rtFtpServerDataConnPrintf(pDataConn, "?"); AssertFailed(); break;
1718 }
1719
1720 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1721 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
1722 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
1723 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
1724 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1725 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
1726 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
1727 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
1728 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1729 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
1730 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
1731 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
1732
1733 rtFtpServerDataConnPrintf(pDataConn, " %*u",
1734 cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks);
1735
1736 if (cchUidCol)
1737 rtFtpServerDataConnPrintf(pDataConn, " %*s", cchUidCol,
1738 rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp));
1739 if (cchGidCol)
1740 rtFtpServerDataConnPrintf(pDataConn, " %*s", cchGidCol,
1741 rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp));
1742
1743 rtFtpServerDataConnPrintf(pDataConn, "%*s", cchSizeCol, rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp));
1744
1745 PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime);
1746 rtFtpServerDataConnPrintf(pDataConn," %s", rtFtpServerFormatTimestamp(pTime, pszTmp, cbTmp));
1747
1748 rtFtpServerDataConnPrintf(pDataConn," %s\r\n", rtFtpServerFormatName(pEntry->szName, pszTmp, cbTmp));
1749 }
1750
1751 return VINF_SUCCESS;
1752}
1753
1754/**
1755 * Thread for handling the LIST command's output in a separate data connection.
1756 *
1757 * @returns VBox status code.
1758 * @param ThreadSelf Thread handle. Unused.
1759 * @param pvUser User-provided arguments. Of type PRTFTPSERVERCLIENT.
1760 */
1761static DECLCALLBACK(int) rtFtpServerDataConnListThread(RTTHREAD ThreadSelf, void *pvUser)
1762{
1763 RT_NOREF(ThreadSelf);
1764
1765 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
1766 AssertPtr(pClient);
1767
1768 PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn;
1769 AssertPtr(pDataConn);
1770
1771 LogFlowFuncEnter();
1772
1773 int rc;
1774
1775 char szTmp[RTPATH_MAX * 2];
1776 PRTFTPDIRCOLLECTION pColl = rtFtpServerDataConnDirCollAlloc();
1777 AssertPtrReturn(pColl, VERR_NO_MEMORY);
1778
1779 /* Set start indicator. */
1780 pDataConn->fStarted = true;
1781
1782 RTThreadUserSignal(RTThreadSelf());
1783
1784 /* The first argument might indicate a directory to list.
1785 * If no argument is given, the implementation must use the last directory set. */
1786 char *pszPath = RTStrDup( pDataConn->cArgs == 1
1787 ? pDataConn->papszArgs[0] : pDataConn->pClient->State.pszCWD); /** @todo Needs locking. */
1788 AssertPtrReturn(pszPath, VERR_NO_MEMORY);
1789 /* The paths already have been validated in the actual command handlers. */
1790
1791 void *pvHandle = NULL; /* Shut up MSVC. */
1792 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirOpen, pszPath, &pvHandle);
1793
1794 for (;;)
1795 {
1796 RTFSOBJINFO objInfo;
1797 RT_ZERO(objInfo);
1798
1799 char *pszEntry = NULL;
1800 char *pszOwner = NULL;
1801 char *pszGroup = NULL;
1802 char *pszTarget = NULL;
1803
1804 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirRead, pvHandle, &pszEntry,
1805 &objInfo, &pszOwner, &pszGroup, &pszTarget);
1806 if (RT_SUCCESS(rc))
1807 {
1808 int rc2 = rtFtpServerDataConnDirCollAddEntry(pColl, pszEntry,
1809 &objInfo, pszOwner, pszGroup, pszTarget);
1810
1811 RTStrFree(pszEntry);
1812 pszEntry = NULL;
1813
1814 RTStrFree(pszOwner);
1815 pszOwner = NULL;
1816
1817 RTStrFree(pszGroup);
1818 pszGroup = NULL;
1819
1820 RTStrFree(pszTarget);
1821 pszTarget = NULL;
1822
1823 if (RT_SUCCESS(rc))
1824 rc = rc2;
1825 }
1826 else
1827 {
1828 if (rc == VERR_NO_MORE_FILES)
1829 {
1830 rc = VINF_SUCCESS;
1831 break;
1832 }
1833 }
1834
1835 if (RT_FAILURE(rc))
1836 break;
1837
1838 if (ASMAtomicReadBool(&pDataConn->fStop))
1839 break;
1840 }
1841
1842 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirClose, pvHandle);
1843 pvHandle = NULL;
1844
1845 rtFtpServerCollSort(pColl);
1846
1847 if (RT_SUCCESS(rc))
1848 {
1849 int rc2 = rtFtpServerDataConnDirCollWrite(pDataConn, pColl, szTmp, sizeof(szTmp));
1850 AssertRC(rc2);
1851 }
1852
1853 rtFtpServerDataConnDirCollFree(pColl);
1854
1855 RTStrFree(pszPath);
1856
1857 pDataConn->fStopped = true;
1858 pDataConn->rc = rc;
1859
1860 LogFlowFuncLeaveRC(rc);
1861 return rc;
1862}
1863
1864static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1865{
1866 /* If no argument is given, use the server's CWD as the path. */
1867 const char *pcszPath = cArgs ? apcszArgs[0] : pClient->State.pszCWD;
1868 AssertPtr(pcszPath);
1869
1870 int rc = VINF_SUCCESS;
1871
1872 if (!rtFtpServerPathIsValid(pcszPath, false /* fIsAbsolute */))
1873 {
1874 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1875 AssertRC(rc2);
1876 }
1877 else
1878 {
1879 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
1880
1881 if (RT_SUCCESS(rc))
1882 {
1883 if (pClient->pDataConn == NULL)
1884 {
1885 rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn);
1886 if (RT_SUCCESS(rc))
1887 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnListThread, cArgs, apcszArgs);
1888
1889 int rc2 = rtFtpServerSendReplyRc( pClient, RT_SUCCESS(rc)
1890 ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN
1891 : RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
1892 AssertRC(rc2);
1893 }
1894 else
1895 {
1896 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN);
1897 AssertRC(rc2);
1898 }
1899 }
1900 else
1901 {
1902 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1903 AssertRC(rc2);
1904 }
1905 }
1906
1907 return rc;
1908}
1909
1910static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1911{
1912 RT_NOREF(pClient, cArgs, apcszArgs);
1913
1914 /** @todo Anything to do here? */
1915 return VINF_SUCCESS;
1916}
1917
1918static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1919{
1920 RT_NOREF(cArgs, apcszArgs);
1921
1922 /* Save timestamp of last command sent. */
1923 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1924
1925 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1926}
1927
1928static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1929{
1930 if (cArgs != 1)
1931 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1932
1933 const char *pcszPassword = apcszArgs[0];
1934 AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
1935
1936 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
1937 if (RT_SUCCESS(rc))
1938 {
1939 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
1940 }
1941 else
1942 {
1943 pClient->State.cFailedLoginAttempts++;
1944
1945 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1946 if (RT_SUCCESS(rc))
1947 rc = rc2;
1948 }
1949
1950 return rc;
1951}
1952
1953static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1954{
1955 if (cArgs != 1)
1956 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1957
1958 RTFTPSERVER_REPLY rcClient;
1959
1960 int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConnAddr, &pClient->uDataConnPort);
1961 if (RT_SUCCESS(rc))
1962 rcClient = RTFTPSERVER_REPLY_OKAY;
1963 else
1964 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
1965
1966 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
1967 if (RT_SUCCESS(rc))
1968 rc = rc2;
1969
1970 return rc;
1971}
1972
1973static int rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1974{
1975 RT_NOREF(cArgs, apcszArgs);
1976
1977 int rc;
1978
1979 char szPWD[RTPATH_MAX];
1980
1981 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
1982
1983 if (RT_SUCCESS(rc))
1984 rc = rtFtpServerSendReplyRcEx(pClient, RTFTPSERVER_REPLY_PATHNAME_OK, "\"%s\"", szPWD); /* See RFC 959, APPENDIX II. */
1985
1986 return rc;
1987}
1988
1989static int rtFtpServerHandleOPTS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1990{
1991 RT_NOREF(cArgs, apcszArgs);
1992
1993 int rc = VINF_SUCCESS;
1994
1995 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1996 if (RT_SUCCESS(rc))
1997 rc = rc2;
1998
1999 return rc;
2000}
2001
2002static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2003{
2004 RT_NOREF(cArgs, apcszArgs);
2005
2006 int rc = VINF_SUCCESS;
2007
2008 if (pClient->pDataConn)
2009 {
2010 rc = rtFtpServerDataConnClose(pClient->pDataConn);
2011 if (RT_SUCCESS(rc))
2012 {
2013 rtFtpServerDataConnDestroy(pClient->pDataConn);
2014 pClient->pDataConn = NULL;
2015 }
2016 }
2017
2018 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2019 if (RT_SUCCESS(rc))
2020 rc = rc2;
2021
2022 return rc;
2023}
2024
2025static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2026{
2027 if (cArgs != 1) /* File name needs to be present. */
2028 return VERR_INVALID_PARAMETER;
2029
2030 int rc;
2031
2032 const char *pcszPath = apcszArgs[0];
2033
2034 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
2035
2036 if (RT_SUCCESS(rc))
2037 {
2038 if (RT_SUCCESS(rc))
2039 {
2040 if (pClient->pDataConn == NULL)
2041 {
2042 rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn);
2043 if (RT_SUCCESS(rc))
2044 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnFileWriteThread, cArgs, apcszArgs);
2045
2046 int rc2 = rtFtpServerSendReplyRc( pClient, RT_SUCCESS(rc)
2047 ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN
2048 : RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
2049 AssertRC(rc2);
2050 }
2051 else
2052 {
2053 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN);
2054 AssertRC(rc2);
2055 }
2056 }
2057 else
2058 {
2059 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
2060 AssertRC(rc2);
2061 }
2062 }
2063
2064 if (RT_FAILURE(rc))
2065 {
2066 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2067 AssertRC(rc2);
2068 }
2069
2070 return rc;
2071}
2072
2073static int rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2074{
2075 if (cArgs != 1)
2076 return VERR_INVALID_PARAMETER;
2077
2078 int rc;
2079
2080 const char *pcszPath = apcszArgs[0];
2081 uint64_t uSize = 0;
2082
2083 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pcszPath, &uSize);
2084
2085 if (RT_SUCCESS(rc))
2086 {
2087 rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
2088 }
2089 else
2090 {
2091 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2092 AssertRC(rc2);
2093 }
2094
2095 return rc;
2096}
2097
2098static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2099{
2100 if (cArgs != 1)
2101 return VERR_INVALID_PARAMETER;
2102
2103 int rc;
2104
2105 RTFSOBJINFO objInfo;
2106 RT_ZERO(objInfo);
2107
2108 const char *pcszPath = apcszArgs[0];
2109
2110 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, &objInfo);
2111
2112 if (RT_SUCCESS(rc))
2113 {
2114 char szFsObjInfo[_4K]; /** @todo Check this size. */
2115 rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
2116 if (RT_SUCCESS(rc))
2117 {
2118 char szFsPathInfo[RTPATH_MAX + 16];
2119 const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pcszPath), pcszPath);
2120 if (cchPathInfo > 0)
2121 {
2122 rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
2123 if (RT_SUCCESS(rc))
2124 rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
2125 }
2126 else
2127 rc = VERR_BUFFER_OVERFLOW;
2128 }
2129 }
2130
2131 if (RT_FAILURE(rc))
2132 {
2133 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2134 AssertRC(rc2);
2135 }
2136
2137 return rc;
2138}
2139
2140static int rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2141{
2142 if (cArgs != 1)
2143 return VERR_INVALID_PARAMETER;
2144
2145 const char *pcszType = apcszArgs[0];
2146
2147 int rc;
2148
2149 if (!RTStrICmp(pcszType, "F"))
2150 {
2151 pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
2152
2153 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2154 }
2155 else
2156 rc = VERR_NOT_IMPLEMENTED;
2157
2158 return rc;
2159}
2160
2161static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2162{
2163 RT_NOREF(cArgs, apcszArgs);
2164
2165 char szOSInfo[64];
2166 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
2167 if (RT_SUCCESS(rc))
2168 rc = rtFtpServerSendReplyStr(pClient, "215 %s", szOSInfo);
2169
2170 return rc;
2171}
2172
2173static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2174{
2175 if (cArgs != 1)
2176 return VERR_INVALID_PARAMETER;
2177
2178 const char *pcszType = apcszArgs[0];
2179
2180 int rc = VINF_SUCCESS;
2181
2182 if (!RTStrICmp(pcszType, "A"))
2183 {
2184 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
2185 }
2186 else if (!RTStrICmp(pcszType, "I")) /* Image (binary). */
2187 {
2188 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE;
2189 }
2190 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
2191 rc = VERR_NOT_IMPLEMENTED;
2192
2193 if (RT_SUCCESS(rc))
2194 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2195
2196 return rc;
2197}
2198
2199static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2200{
2201 if (cArgs != 1)
2202 return VERR_INVALID_PARAMETER;
2203
2204 const char *pcszUser = apcszArgs[0];
2205 AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
2206
2207 rtFtpServerClientStateReset(&pClient->State);
2208
2209 int rc = rtFtpServerLookupUser(pClient, pcszUser);
2210 if (RT_SUCCESS(rc))
2211 {
2212 pClient->State.pszUser = RTStrDup(pcszUser);
2213 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
2214
2215 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
2216 }
2217 else
2218 {
2219 pClient->State.cFailedLoginAttempts++;
2220
2221 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
2222 if (RT_SUCCESS(rc))
2223 rc = rc2;
2224 }
2225
2226 return rc;
2227}
2228
2229
2230/*********************************************************************************************************************************
2231* Internal server functions *
2232*********************************************************************************************************************************/
2233
2234/**
2235 * Parses FTP command arguments handed in by the client.
2236 *
2237 * @returns VBox status code.
2238 * @param pcszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
2239 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
2240 * @param ppapcszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
2241 */
2242static int rtFtpServerCmdArgsParse(const char *pcszCmdParms, uint8_t *pcArgs, char ***ppapcszArgs)
2243{
2244 *pcArgs = 0;
2245 *ppapcszArgs = NULL;
2246
2247 if (!pcszCmdParms) /* No parms given? Bail out early. */
2248 return VINF_SUCCESS;
2249
2250 /** @todo Anything else to do here? */
2251 /** @todo Check if quoting is correct. */
2252
2253 int cArgs = 0;
2254 int rc = RTGetOptArgvFromString(ppapcszArgs, &cArgs, pcszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
2255 if (RT_SUCCESS(rc))
2256 {
2257 if (cArgs <= UINT8_MAX)
2258 {
2259 *pcArgs = (uint8_t)cArgs;
2260 }
2261 else
2262 rc = VERR_INVALID_PARAMETER;
2263 }
2264
2265 return rc;
2266}
2267
2268/**
2269 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
2270 *
2271 * @param ppapcszArgs Argument string array to free.
2272 */
2273static void rtFtpServerCmdArgsFree(char **ppapcszArgs)
2274{
2275 RTGetOptArgvFree(ppapcszArgs);
2276}
2277
2278/**
2279 * Main function for processing client commands for the control connection.
2280 *
2281 * @returns VBox status code.
2282 * @param pClient Client to process commands for.
2283 * @param pcszCmd Command string to parse and handle.
2284 * @param cbCmd Size (in bytes) of command string.
2285 */
2286static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pcszCmd, size_t cbCmd)
2287{
2288 /* Make sure to terminate the string in any case. */
2289 pcszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0';
2290
2291 /* A tiny bit of sanitation. */
2292 RTStrStripL(pcszCmd);
2293
2294 /* First, terminate string by finding the command end marker (telnet style). */
2295 /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
2296 char *pszCmdEnd = RTStrIStr(pcszCmd, "\r\n");
2297 if (pszCmdEnd)
2298 *pszCmdEnd = '\0';
2299
2300 /* Reply which gets sent back to the client. */
2301 RTFTPSERVER_REPLY rcClient = RTFTPSERVER_REPLY_INVALID;
2302
2303 int rcCmd = VINF_SUCCESS;
2304
2305 uint8_t cArgs = 0;
2306 char **papszArgs = NULL;
2307 int rc = rtFtpServerCmdArgsParse(pcszCmd, &cArgs, &papszArgs);
2308 if ( RT_SUCCESS(rc)
2309 && cArgs) /* At least the actual command (without args) must be present. */
2310 {
2311 LogFlowFunc(("Handling command '%s'\n", papszArgs[0]));
2312 for (uint8_t a = 0; a < cArgs; a++)
2313 LogFlowFunc(("\targ[%RU8] = '%s'\n", a, papszArgs[a]));
2314
2315 unsigned i = 0;
2316 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
2317 {
2318 const RTFTPSERVER_CMD_ENTRY *pCmdEntry = &g_aCmdMap[i];
2319
2320 if (!RTStrICmp(papszArgs[0], pCmdEntry->szCmd))
2321 {
2322 /* Some commands need a valid user before they can be executed. */
2323 if ( pCmdEntry->fNeedsUser
2324 && pClient->State.pszUser == NULL)
2325 {
2326 rcClient = RTFTPSERVER_REPLY_NOT_LOGGED_IN;
2327 break;
2328 }
2329
2330 /* Save timestamp of last command sent. */
2331 pClient->State.tsLastCmdMs = RTTimeMilliTS();
2332
2333 /* Hand in arguments only without the actual command. */
2334 rcCmd = pCmdEntry->pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
2335 if (RT_FAILURE(rcCmd))
2336 {
2337 LogFunc(("Handling command '%s' failed with %Rrc\n", papszArgs[0], rcCmd));
2338
2339 switch (rcCmd)
2340 {
2341 case VERR_INVALID_PARAMETER:
2342 RT_FALL_THROUGH();
2343 case VERR_INVALID_POINTER:
2344 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
2345 break;
2346
2347 case VERR_NOT_IMPLEMENTED:
2348 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
2349 break;
2350
2351 default:
2352 break;
2353 }
2354 }
2355 break;
2356 }
2357 }
2358
2359 rtFtpServerCmdArgsFree(papszArgs);
2360
2361 if (i == RT_ELEMENTS(g_aCmdMap))
2362 {
2363 LogFlowFunc(("Command not implemented\n"));
2364 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
2365 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
2366 }
2367
2368 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
2369 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
2370 if (fDisconnect)
2371 {
2372 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser);
2373
2374 rtFtpServerClientStateReset(&pClient->State);
2375
2376 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
2377 rcClient = RTFTPSERVER_REPLY_CLOSING_CTRL_CONN;
2378 }
2379 }
2380 else
2381 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
2382
2383 if (rcClient != RTFTPSERVER_REPLY_INVALID)
2384 {
2385 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
2386 if (RT_SUCCESS(rc))
2387 rc = rc2;
2388 }
2389
2390 LogFlowFuncLeaveRC(rc);
2391 return rc;
2392}
2393
2394/**
2395 * Main loop for processing client commands.
2396 *
2397 * @returns VBox status code.
2398 * @param pClient Client to process commands for.
2399 */
2400static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
2401{
2402 int rc;
2403
2404 size_t cbRead;
2405 char szCmd[RTFTPSERVER_MAX_CMD_LEN + 1];
2406
2407 for (;;)
2408 {
2409 rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */
2410 if (RT_SUCCESS(rc))
2411 {
2412 rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
2413 if ( RT_SUCCESS(rc)
2414 && cbRead)
2415 {
2416 AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW);
2417 rc = rtFtpServerProcessCommands(pClient, szCmd, cbRead);
2418 }
2419 }
2420 else
2421 {
2422 if (rc == VERR_TIMEOUT)
2423 rc = VINF_SUCCESS;
2424
2425 if (RT_FAILURE(rc))
2426 break;
2427 }
2428
2429 /*
2430 * Handle data connection replies.
2431 */
2432 if (pClient->pDataConn)
2433 {
2434 if ( ASMAtomicReadBool(&pClient->pDataConn->fStarted)
2435 && ASMAtomicReadBool(&pClient->pDataConn->fStopped))
2436 {
2437 Assert(pClient->pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS);
2438
2439 int rc2 = rtFtpServerSendReplyRc(pClient,
2440 RT_SUCCESS(pClient->pDataConn->rc)
2441 ? RTFTPSERVER_REPLY_CLOSING_DATA_CONN : RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
2442 AssertRC(rc2);
2443
2444 rc = rtFtpServerDataConnStop(pClient->pDataConn);
2445 if (RT_SUCCESS(rc))
2446 {
2447 rtFtpServerDataConnDestroy(pClient->pDataConn);
2448 pClient->pDataConn = NULL;
2449 }
2450 }
2451 }
2452 }
2453
2454 /* Make sure to destroy all data connections. */
2455 rtFtpServerDataConnDestroy(pClient->pDataConn);
2456 pClient->pDataConn = NULL;
2457
2458 LogFlowFuncLeaveRC(rc);
2459 return rc;
2460}
2461
2462/**
2463 * Resets the client's state.
2464 *
2465 * @param pState Client state to reset.
2466 */
2467static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
2468{
2469 LogFlowFuncEnter();
2470
2471 RTStrFree(pState->pszUser);
2472 pState->pszUser = NULL;
2473
2474 int rc2 = rtFtpSetCWD(pState, "/");
2475 AssertRC(rc2);
2476
2477 pState->cFailedLoginAttempts = 0;
2478 pState->tsLastCmdMs = RTTimeMilliTS();
2479 pState->enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
2480 pState->enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
2481}
2482
2483/**
2484 * Per-client thread for serving the server's control connection.
2485 *
2486 * @returns VBox status code.
2487 * @param hSocket Socket handle to use for the control connection.
2488 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
2489 */
2490static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
2491{
2492 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
2493 RTFTPSERVER_VALID_RETURN(pThis);
2494
2495 RTFTPSERVERCLIENT Client;
2496 RT_ZERO(Client);
2497
2498 Client.pServer = pThis;
2499 Client.hSocket = hSocket;
2500
2501 LogFlowFunc(("New client connected\n"));
2502
2503 rtFtpServerClientStateReset(&Client.State);
2504
2505 /*
2506 * Send welcome message.
2507 * Note: Some clients (like FileZilla / Firefox) expect a message together with the reply code,
2508 * so make sure to include at least *something*.
2509 */
2510 int rc = rtFtpServerSendReplyRcEx(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER,
2511 "Welcome!");
2512 if (RT_SUCCESS(rc))
2513 {
2514 ASMAtomicIncU32(&pThis->cClients);
2515
2516 rc = rtFtpServerProcessCommands(&Client);
2517
2518 ASMAtomicDecU32(&pThis->cClients);
2519 }
2520
2521 rtFtpServerClientStateReset(&Client.State);
2522
2523 return rc;
2524}
2525
2526RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
2527 PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
2528{
2529 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
2530 AssertPtrReturn(pcszAddress, VERR_INVALID_POINTER);
2531 AssertReturn (uPort, VERR_INVALID_PARAMETER);
2532 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
2533 /* pvUser is optional. */
2534
2535 int rc;
2536
2537 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
2538 if (pThis)
2539 {
2540 pThis->u32Magic = RTFTPSERVER_MAGIC;
2541 pThis->Callbacks = *pCallbacks;
2542 pThis->pvUser = pvUser;
2543 pThis->cbUser = cbUser;
2544
2545 rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
2546 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
2547 if (RT_SUCCESS(rc))
2548 {
2549 *phFTPServer = (RTFTPSERVER)pThis;
2550 }
2551 }
2552 else
2553 rc = VERR_NO_MEMORY;
2554
2555 return rc;
2556}
2557
2558RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
2559{
2560 if (hFTPServer == NIL_RTFTPSERVER)
2561 return VINF_SUCCESS;
2562
2563 PRTFTPSERVERINTERNAL pThis = hFTPServer;
2564 RTFTPSERVER_VALID_RETURN(pThis);
2565
2566 AssertPtr(pThis->pTCPServer);
2567
2568 int rc = RTTcpServerDestroy(pThis->pTCPServer);
2569 if (RT_SUCCESS(rc))
2570 {
2571 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
2572
2573 RTMemFree(pThis);
2574 }
2575
2576 return rc;
2577}
2578
Note: See TracBrowser for help on using the repository browser.

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