VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/ftp-server.cpp@ 90330

Last change on this file since 90330 was 86981, checked in by vboxsync, 4 years ago

IPRT/FTP: Docs nit (has been resolved). bugref:9646

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