VirtualBox

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

Last change on this file since 86208 was 85676, checked in by vboxsync, 4 years ago

IPRT/ftp-server.cpp: some nits and todos. bugref:9437

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