VirtualBox

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

Last change on this file since 98278 was 98103, checked in by vboxsync, 2 years ago

Copyright year updates by scm.

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