VirtualBox

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

Last change on this file since 107721 was 107721, checked in by vboxsync, 7 weeks ago

src/VBox/Runtime/r3/ftp-server.cpp: Fixed warnings found by Parfait (unused assignment + unread variable). jiraref:VBP-1424

  • 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 107721 2025-01-13 19:18:55Z 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-2024 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 * @param pDataConn Data connection to destroy. The pointer is not valid anymore after successful return.
1244 */
1245static void rtFtpServerDataConnDestroy(PRTFTPSERVERDATACONN pDataConn)
1246{
1247 if (!pDataConn)
1248 return;
1249
1250 LogFlowFuncEnter();
1251
1252 rtFtpServerDataConnClose(pDataConn);
1253 rtFtpCmdArgsFree(pDataConn->cArgs, pDataConn->papszArgs);
1254
1255 RTCircBufDestroy(pDataConn->pCircBuf);
1256
1257 RTMemFree(pDataConn);
1258 pDataConn = NULL;
1259
1260 LogFlowFuncLeave();
1261 return;
1262}
1263
1264/**
1265 * Resets a data connection structure.
1266 *
1267 * @param pDataConn Data connection structure to reset.
1268 */
1269static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn)
1270{
1271 LogFlowFuncEnter();
1272
1273 pDataConn->hSocket = NIL_RTSOCKET;
1274 pDataConn->uPort = 20; /* Default port to use. */
1275 pDataConn->hThread = NIL_RTTHREAD;
1276 pDataConn->fStarted = false;
1277 pDataConn->fStop = false;
1278 pDataConn->fStopped = false;
1279 pDataConn->rc = VERR_IPE_UNINITIALIZED_STATUS;
1280}
1281
1282
1283/*********************************************************************************************************************************
1284* Command Protocol Handlers *
1285*********************************************************************************************************************************/
1286
1287static DECLCALLBACK(int) rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1288{
1289 RT_NOREF(cArgs, apszArgs);
1290
1291 int rc = rtFtpServerDataConnClose(pClient->pDataConn);
1292 if (RT_SUCCESS(rc))
1293 {
1294 rtFtpServerDataConnDestroy(pClient->pDataConn);
1295 pClient->pDataConn = NULL;
1296
1297 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1298 }
1299
1300 return rc;
1301}
1302
1303static DECLCALLBACK(int) rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1304{
1305 RT_NOREF(cArgs, apszArgs);
1306
1307 int rc;
1308
1309 RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp);
1310
1311 if (RT_SUCCESS(rc))
1312 {
1313 const size_t cbPath = sizeof(char) * RTFTPSERVER_MAX_PATH;
1314 char *pszPath = RTStrAlloc(cbPath);
1315 if (pszPath)
1316 {
1317 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, pszPath, cbPath);
1318
1319 if (RT_SUCCESS(rc))
1320 {
1321 rc = rtFtpSetCWD(&pClient->State, pszPath);
1322 if (RT_SUCCESS(rc))
1323 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1324 }
1325
1326 RTStrFree(pszPath);
1327
1328 }
1329 else
1330 rc = VERR_NO_MEMORY;
1331 }
1332
1333 if (RT_FAILURE(rc))
1334 {
1335 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1336 AssertRC(rc2);
1337 }
1338
1339 return rc;
1340}
1341
1342static DECLCALLBACK(int) rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1343{
1344 if (cArgs != 1)
1345 return VERR_INVALID_PARAMETER;
1346
1347 int rc;
1348
1349 const char *pszPath = apszArgs[0];
1350
1351 if (!rtFtpServerPathIsValid(pszPath, false /* fIsAbsolute */))
1352 return VERR_INVALID_PARAMETER;
1353
1354 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, pszPath);
1355
1356 if (RT_SUCCESS(rc))
1357 rc = rtFtpSetCWD(&pClient->State, pszPath);
1358
1359 return rtFtpServerSendReplyRc(pClient,
1360 RT_SUCCESS(rc)
1361 ? RTFTPSERVER_REPLY_OKAY : RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1362}
1363
1364static DECLCALLBACK(int) rtFtpServerHandleFEAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1365{
1366 RT_NOREF(cArgs, apszArgs);
1367
1368 int rc = rtFtpServerSendReplyStr(pClient, "211-BEGIN Features:");
1369 if (RT_SUCCESS(rc))
1370 {
1371 rc = rtFtpServerSendReplyStr(pClient, RTFTPSERVER_FEATURES_STRING);
1372 if (RT_SUCCESS(rc))
1373 rc = rtFtpServerSendReplyStr(pClient, "211 END Features");
1374 }
1375
1376 return rc;
1377}
1378
1379/**
1380 * Formats the given user ID according to the specified options.
1381 *
1382 * @returns pszDst
1383 * @param uid The UID to format.
1384 * @param pszOwner The owner returned by the FS.
1385 * @param pszDst The output buffer.
1386 * @param cbDst The output buffer size.
1387 */
1388static const char *rtFtpServerDecimalFormatOwner(RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst)
1389{
1390 if (pszOwner)
1391 {
1392 RTStrCopy(pszDst, cbDst, pszOwner);
1393 return pszDst;
1394 }
1395 if (uid == NIL_RTUID)
1396 return "<Nil>";
1397
1398 RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0);
1399 return pszDst;
1400}
1401
1402/**
1403 * Formats the given group ID according to the specified options.
1404 *
1405 * @returns pszDst
1406 * @param gid The GID to format.
1407 * @param pszGroup The group returned by the FS.
1408 * @param pszDst The output buffer.
1409 * @param cbDst The output buffer size.
1410 */
1411static const char *rtFtpServerDecimalFormatGroup(RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst)
1412{
1413 if (pszGroup)
1414 {
1415 RTStrCopy(pszDst, cbDst, pszGroup);
1416 return pszDst;
1417 }
1418 if (gid == NIL_RTGID)
1419 return "<Nil>";
1420
1421 RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0);
1422 return pszDst;
1423}
1424
1425/**
1426 * Format file size.
1427 */
1428static const char *rtFtpServerFormatSize(uint64_t cb, char *pszDst, size_t cbDst)
1429{
1430 RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
1431 return pszDst;
1432}
1433
1434/**
1435 * Formats the given timestamp according to (non-standardized) FTP LIST command.
1436 *
1437 * @returns pszDst
1438 * @param pTimestamp The timestamp to format.
1439 * @param pszDst The output buffer.
1440 * @param cbDst The output buffer size.
1441 */
1442static const char *rtFtpServerFormatTimestamp(PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst)
1443{
1444 RTTIME Time;
1445 RTTimeExplode(&Time, pTimestamp);
1446
1447 /* Calc the UTC offset part. */
1448 int32_t offUtc = Time.offUTC;
1449 Assert(offUtc <= 840 && offUtc >= -840);
1450 if (offUtc < 0)
1451 offUtc = -offUtc;
1452 uint32_t offUtcHour = (uint32_t)offUtc / 60;
1453 uint32_t offUtcMinute = (uint32_t)offUtc % 60;
1454
1455 /** @todo Cache this. */
1456 RTTIMESPEC TimeSpecNow;
1457 RTTimeNow(&TimeSpecNow);
1458 RTTIME TimeNow;
1459 RTTimeExplode(&TimeNow, &TimeSpecNow);
1460
1461 /* Only include the year if it's not the same year as today. */
1462 if (TimeNow.i32Year != Time.i32Year)
1463 {
1464 RTStrPrintf(pszDst, cbDst, "%s %02RU8 %5RU32",
1465 g_apszMonths[Time.u8Month], Time.u8MonthDay, Time.i32Year);
1466 }
1467 else /* ... otherwise include the (rough) time (as GMT). */
1468 {
1469 RTStrPrintf(pszDst, cbDst, "%s %02RU8 %02RU32:%02RU32",
1470 g_apszMonths[Time.u8Month], Time.u8MonthDay, offUtcHour, offUtcMinute);
1471 }
1472
1473 return pszDst;
1474}
1475
1476/**
1477 * Format name, i.e. escape, hide, quote stuff.
1478 */
1479static const char *rtFtpServerFormatName(const char *pszName, char *pszDst, size_t cbDst)
1480{
1481 /** @todo implement name formatting. */
1482 RT_NOREF(pszDst, cbDst);
1483 return pszName;
1484}
1485
1486/**
1487 * Figures out the length for a 32-bit number when formatted as decimal.
1488 * @returns Number of digits.
1489 * @param uValue The number.
1490 */
1491DECLINLINE(size_t) rtFtpServerDecimalFormatLengthU32(uint32_t uValue)
1492{
1493 if (uValue < 10)
1494 return 1;
1495 if (uValue < 100)
1496 return 2;
1497 if (uValue < 1000)
1498 return 3;
1499 if (uValue < 10000)
1500 return 4;
1501 if (uValue < 100000)
1502 return 5;
1503 if (uValue < 1000000)
1504 return 6;
1505 if (uValue < 10000000)
1506 return 7;
1507 if (uValue < 100000000)
1508 return 8;
1509 if (uValue < 1000000000)
1510 return 9;
1511 return 10;
1512}
1513
1514/**
1515 * Allocates a new directory collection.
1516 *
1517 * @returns The collection allocated.
1518 */
1519static PRTFTPDIRCOLLECTION rtFtpServerDataConnDirCollAlloc(void)
1520{
1521 return (PRTFTPDIRCOLLECTION)RTMemAllocZ(sizeof(RTFTPDIRCOLLECTION));
1522}
1523
1524/**
1525 * Frees a directory collection and its entries.
1526 *
1527 * @param pCollection The collection to free.
1528 */
1529static void rtFtpServerDataConnDirCollFree(PRTFTPDIRCOLLECTION pCollection)
1530{
1531 PPRTFTPDIRENTRY papEntries = pCollection->papEntries;
1532 size_t j = pCollection->cEntries;
1533 while (j-- > 0)
1534 {
1535 RTMemFree(papEntries[j]);
1536 papEntries[j] = NULL;
1537 }
1538 RTMemFree(papEntries);
1539 pCollection->papEntries = NULL;
1540 pCollection->cEntries = 0;
1541 pCollection->cEntriesAllocated = 0;
1542 RTMemFree(pCollection);
1543}
1544
1545/**
1546 * Adds one entry to a collection.
1547 *
1548 * @returns VBox status code.
1549 * @param pCollection The collection to add entry to.
1550 * @param pszEntry The entry name.
1551 * @param pInfo The entry info.
1552 * @param pszOwner The owner name if available, otherwise NULL.
1553 * @param pszGroup The group anme if available, otherwise NULL.
1554 * @param pszTarget The symbolic link target if applicable and
1555 * available, otherwise NULL.
1556 */
1557static int rtFtpServerDataConnDirCollAddEntry(PRTFTPDIRCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo,
1558 const char *pszOwner, const char *pszGroup, const char *pszTarget)
1559{
1560 /* Filter out entries we don't want to report to the client, even if they were reported by the actual implementation. */
1561 if ( !RTStrCmp(pszEntry, ".")
1562 || !RTStrCmp(pszEntry, ".."))
1563 {
1564 return VINF_SUCCESS;
1565 }
1566
1567 /* Anything else besides files and directores is not allowed; just don't show them at all for the moment. */
1568 switch (pInfo->Attr.fMode & RTFS_TYPE_MASK)
1569 {
1570 case RTFS_TYPE_DIRECTORY:
1571 RT_FALL_THROUGH();
1572 case RTFS_TYPE_FILE:
1573 break;
1574
1575 default:
1576 return VINF_SUCCESS;
1577 }
1578
1579 /* Make sure there is space in the collection for the new entry. */
1580 if (pCollection->cEntries >= pCollection->cEntriesAllocated)
1581 {
1582 size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16;
1583 void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0]));
1584 if (!pvNew)
1585 return VERR_NO_MEMORY;
1586 pCollection->papEntries = (PPRTFTPDIRENTRY)pvNew;
1587 pCollection->cEntriesAllocated = cNew;
1588 }
1589
1590 /* Create and insert a new entry. */
1591 size_t const cchEntry = strlen(pszEntry);
1592 size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0;
1593 size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0;
1594 size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0;
1595 size_t const cbEntry = RT_UOFFSETOF_DYN(RTFTPDIRENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]);
1596 PRTFTPDIRENTRY pEntry = (PRTFTPDIRENTRY)RTMemAlloc(cbEntry);
1597 if (pEntry)
1598 {
1599 pEntry->Info = *pInfo;
1600 pEntry->pszTarget = NULL; /** @todo symbolic links. */
1601 pEntry->pszOwner = NULL;
1602 pEntry->pszGroup = NULL;
1603 pEntry->cchName = cchEntry;
1604 memcpy(pEntry->szName, pszEntry, cchEntry);
1605 pEntry->szName[cchEntry] = '\0';
1606
1607 char *psz = &pEntry->szName[cchEntry + 1];
1608 if (pszTarget)
1609 {
1610 pEntry->pszTarget = psz;
1611 memcpy(psz, pszTarget, cbTarget);
1612 psz += cbTarget;
1613 }
1614 if (pszOwner)
1615 {
1616 pEntry->pszOwner = psz;
1617 memcpy(psz, pszOwner, cbOwner);
1618 psz += cbOwner;
1619 }
1620 if (pszGroup)
1621 {
1622 pEntry->pszGroup = psz;
1623 memcpy(psz, pszGroup, cbGroup);
1624 }
1625
1626 pCollection->papEntries[pCollection->cEntries++] = pEntry;
1627 pCollection->cbTotalAllocated += pEntry->Info.cbAllocated;
1628 pCollection->cbTotalFiles += pEntry->Info.cbObject;
1629 return VINF_SUCCESS;
1630 }
1631 return VERR_NO_MEMORY;
1632}
1633
1634/** @callback_method_impl{FNRTSORTCMP, Name} */
1635static DECLCALLBACK(int) rtFtpServerCollEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser)
1636{
1637 RT_NOREF(pvUser);
1638 PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1;
1639 PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2;
1640 return RTStrCmp(pEntry1->szName, pEntry2->szName);
1641}
1642
1643/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */
1644static DECLCALLBACK(int) rtFtpServerCollEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser)
1645{
1646 RT_NOREF(pvUser);
1647 PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1;
1648 PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2;
1649 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
1650 if (!iDiff)
1651 iDiff = rtFtpServerCollEntryCmpName(pEntry1, pEntry2, pvUser);
1652 return iDiff;
1653}
1654
1655/**
1656 * Sorts a given directory collection according to the FTP server's LIST style.
1657 *
1658 * @param pCollection Collection to sort.
1659 */
1660static void rtFtpServerCollSort(PRTFTPDIRCOLLECTION pCollection)
1661{
1662 PFNRTSORTCMP pfnCmp = rtFtpServerCollEntryCmpDirFirstName;
1663 if (pfnCmp)
1664 RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL);
1665}
1666
1667/**
1668 * Writes a directory collection to a specific data connection.
1669 *
1670 * @returns VBox status code.
1671 * @param pDataConn Data connection to write directory collection to.
1672 * @param pCollection Collection to write.
1673 * @param pszTmp Temporary buffer used for writing.
1674 * @param cbTmp Size (in bytes) of temporary buffer used for writing.
1675 */
1676static int rtFtpServerDataConnDirCollWrite(PRTFTPSERVERDATACONN pDataConn, PRTFTPDIRCOLLECTION pCollection,
1677 char *pszTmp, size_t cbTmp)
1678{
1679 size_t cchSizeCol = 4;
1680 size_t cchLinkCol = 1;
1681 size_t cchUidCol = 1;
1682 size_t cchGidCol = 1;
1683
1684 size_t i = pCollection->cEntries;
1685 while (i-- > 0)
1686 {
1687 PRTFTPDIRENTRY pEntry = pCollection->papEntries[i];
1688
1689 rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp);
1690 size_t cchTmp = strlen(pszTmp);
1691 if (cchTmp > cchSizeCol)
1692 cchSizeCol = cchTmp;
1693
1694 cchTmp = rtFtpServerDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1;
1695 if (cchTmp > cchLinkCol)
1696 cchLinkCol = cchTmp;
1697
1698 rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp);
1699 cchTmp = strlen(pszTmp);
1700 if (cchTmp > cchUidCol)
1701 cchUidCol = cchTmp;
1702
1703 rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp);
1704 cchTmp = strlen(pszTmp);
1705 if (cchTmp > cchGidCol)
1706 cchGidCol = cchTmp;
1707 }
1708
1709 size_t offTime = RT_UOFFSETOF(RTFTPDIRENTRY, Info.ModificationTime);
1710
1711 /*
1712 * Display the entries.
1713 */
1714 for (i = 0; i < pCollection->cEntries; i++)
1715 {
1716 PRTFTPDIRENTRY pEntry = pCollection->papEntries[i];
1717
1718 RTFMODE fMode = pEntry->Info.Attr.fMode;
1719 switch (fMode & RTFS_TYPE_MASK)
1720 {
1721 case RTFS_TYPE_FIFO: rtFtpServerDataConnPrintf(pDataConn, "f"); break;
1722 case RTFS_TYPE_DEV_CHAR: rtFtpServerDataConnPrintf(pDataConn, "c"); break;
1723 case RTFS_TYPE_DIRECTORY: rtFtpServerDataConnPrintf(pDataConn, "d"); break;
1724 case RTFS_TYPE_DEV_BLOCK: rtFtpServerDataConnPrintf(pDataConn, "b"); break;
1725 case RTFS_TYPE_FILE: rtFtpServerDataConnPrintf(pDataConn, "-"); break;
1726 case RTFS_TYPE_SYMLINK: rtFtpServerDataConnPrintf(pDataConn, "l"); break;
1727 case RTFS_TYPE_SOCKET: rtFtpServerDataConnPrintf(pDataConn, "s"); break;
1728 case RTFS_TYPE_WHITEOUT: rtFtpServerDataConnPrintf(pDataConn, "w"); break;
1729 default: rtFtpServerDataConnPrintf(pDataConn, "?"); AssertFailed(); break;
1730 }
1731
1732 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1733 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
1734 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
1735 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
1736 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1737 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
1738 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
1739 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
1740 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1741 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
1742 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
1743 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
1744
1745 rtFtpServerDataConnPrintf(pDataConn, " %*u",
1746 cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks);
1747
1748 if (cchUidCol)
1749 rtFtpServerDataConnPrintf(pDataConn, " %*s", cchUidCol,
1750 rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp));
1751 if (cchGidCol)
1752 rtFtpServerDataConnPrintf(pDataConn, " %*s", cchGidCol,
1753 rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp));
1754
1755 rtFtpServerDataConnPrintf(pDataConn, "%*s", cchSizeCol, rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp));
1756
1757 PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime);
1758 rtFtpServerDataConnPrintf(pDataConn," %s", rtFtpServerFormatTimestamp(pTime, pszTmp, cbTmp));
1759
1760 rtFtpServerDataConnPrintf(pDataConn," %s\r\n", rtFtpServerFormatName(pEntry->szName, pszTmp, cbTmp));
1761 }
1762
1763 return VINF_SUCCESS;
1764}
1765
1766/**
1767 * Thread for handling the LIST command's output in a separate data connection.
1768 *
1769 * @returns VBox status code.
1770 * @param ThreadSelf Thread handle. Unused.
1771 * @param pvUser User-provided arguments. Of type PRTFTPSERVERCLIENT.
1772 */
1773static DECLCALLBACK(int) rtFtpServerDataConnListThread(RTTHREAD ThreadSelf, void *pvUser)
1774{
1775 RT_NOREF(ThreadSelf);
1776
1777 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
1778 AssertPtr(pClient);
1779
1780 PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn;
1781 AssertPtr(pDataConn);
1782
1783 LogFlowFuncEnter();
1784
1785 int rc;
1786
1787 char szTmp[RTPATH_MAX * 2];
1788 PRTFTPDIRCOLLECTION pColl = rtFtpServerDataConnDirCollAlloc();
1789 AssertPtrReturn(pColl, VERR_NO_MEMORY);
1790
1791 /* Set start indicator. */
1792 pDataConn->fStarted = true;
1793
1794 RTThreadUserSignal(RTThreadSelf());
1795
1796 /* The first argument might indicate a directory to list.
1797 * If no argument is given, the implementation must use the last directory set. */
1798 char *pszPath = RTStrDup( pDataConn->cArgs == 1
1799 ? pDataConn->papszArgs[0] : pDataConn->pClient->State.pszCWD); /** @todo Needs locking. */
1800 AssertPtrReturnStmt(pszPath, rtFtpServerDataConnDirCollFree(pColl), VERR_NO_MEMORY);
1801 /* The paths already have been validated in the actual command handlers. */
1802
1803 void *pvHandle = NULL; /* Shut up MSVC. */
1804 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirOpen, pszPath, &pvHandle);
1805
1806 for (;;)
1807 {
1808 RTFSOBJINFO objInfo;
1809 RT_ZERO(objInfo);
1810
1811 char *pszEntry = NULL;
1812 char *pszOwner = NULL;
1813 char *pszGroup = NULL;
1814 char *pszTarget = NULL;
1815
1816 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirRead, pvHandle, &pszEntry,
1817 &objInfo, &pszOwner, &pszGroup, &pszTarget);
1818 if (RT_SUCCESS(rc))
1819 {
1820 int rc2 = rtFtpServerDataConnDirCollAddEntry(pColl, pszEntry,
1821 &objInfo, pszOwner, pszGroup, pszTarget);
1822
1823 RTStrFree(pszEntry);
1824 pszEntry = NULL;
1825
1826 RTStrFree(pszOwner);
1827 pszOwner = NULL;
1828
1829 RTStrFree(pszGroup);
1830 pszGroup = NULL;
1831
1832 RTStrFree(pszTarget);
1833 pszTarget = NULL;
1834
1835 if (RT_SUCCESS(rc))
1836 rc = rc2;
1837 }
1838 else
1839 {
1840 if (rc == VERR_NO_MORE_FILES)
1841 {
1842 rc = VINF_SUCCESS;
1843 break;
1844 }
1845 }
1846
1847 if (RT_FAILURE(rc))
1848 break;
1849
1850 if (ASMAtomicReadBool(&pDataConn->fStop))
1851 break;
1852 }
1853
1854 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirClose, pvHandle);
1855 pvHandle = NULL;
1856
1857 rtFtpServerCollSort(pColl);
1858
1859 if (RT_SUCCESS(rc))
1860 {
1861 int rc2 = rtFtpServerDataConnDirCollWrite(pDataConn, pColl, szTmp, sizeof(szTmp));
1862 AssertRC(rc2);
1863 }
1864
1865 rtFtpServerDataConnDirCollFree(pColl);
1866
1867 RTStrFree(pszPath);
1868
1869 pDataConn->fStopped = true;
1870 pDataConn->rc = rc;
1871
1872 LogFlowFuncLeaveRC(rc);
1873 return rc;
1874}
1875
1876static DECLCALLBACK(int) rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1877{
1878 /* If no argument is given, use the server's CWD as the path. */
1879 const char *pszPath = cArgs ? apszArgs[0] : pClient->State.pszCWD;
1880 AssertPtr(pszPath);
1881
1882 int rc = VINF_SUCCESS;
1883
1884 if (!rtFtpServerPathIsValid(pszPath, false /* fIsAbsolute */))
1885 {
1886 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1887 AssertRC(rc2);
1888 }
1889 else
1890 {
1891 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pszPath, NULL /* PRTFSOBJINFO */);
1892
1893 if (RT_SUCCESS(rc))
1894 {
1895 if (pClient->pDataConn == NULL)
1896 {
1897 rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn);
1898 if (RT_SUCCESS(rc))
1899 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnListThread, cArgs, apszArgs);
1900
1901 int rc2 = rtFtpServerSendReplyRc( pClient, RT_SUCCESS(rc)
1902 ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN
1903 : RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
1904 AssertRC(rc2);
1905 }
1906 else
1907 {
1908 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN);
1909 AssertRC(rc2);
1910 }
1911 }
1912 else
1913 {
1914 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1915 AssertRC(rc2);
1916 }
1917 }
1918
1919 return rc;
1920}
1921
1922static DECLCALLBACK(int) rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1923{
1924 RT_NOREF(pClient, cArgs, apszArgs);
1925
1926 /** @todo Anything to do here? */
1927 return VINF_SUCCESS;
1928}
1929
1930static DECLCALLBACK(int) rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1931{
1932 RT_NOREF(cArgs, apszArgs);
1933
1934 /* Save timestamp of last command sent. */
1935 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1936
1937 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1938}
1939
1940static DECLCALLBACK(int) rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1941{
1942 if (cArgs != 1)
1943 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1944
1945 const char *pszPassword = apszArgs[0];
1946 AssertPtrReturn(pszPassword, VERR_INVALID_PARAMETER);
1947
1948 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pszPassword);
1949 if (RT_SUCCESS(rc))
1950 {
1951 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
1952 }
1953 else
1954 {
1955 pClient->State.cFailedLoginAttempts++;
1956
1957 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1958 if (RT_SUCCESS(rc))
1959 rc = rc2;
1960 }
1961
1962 return rc;
1963}
1964
1965static DECLCALLBACK(int) rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1966{
1967 if (cArgs != 1)
1968 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1969
1970 RTFTPSERVER_REPLY rcClient;
1971
1972 int rc = rtFtpParseHostAndPort(apszArgs[0], &pClient->DataConnAddr, &pClient->uDataConnPort);
1973 if (RT_SUCCESS(rc))
1974 rcClient = RTFTPSERVER_REPLY_OKAY;
1975 else
1976 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
1977
1978 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
1979 if (RT_SUCCESS(rc))
1980 rc = rc2;
1981
1982 return rc;
1983}
1984
1985static DECLCALLBACK(int) rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
1986{
1987 RT_NOREF(cArgs, apszArgs);
1988
1989 int rc;
1990
1991 char szPWD[RTPATH_MAX];
1992
1993 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
1994
1995 if (RT_SUCCESS(rc))
1996 rc = rtFtpServerSendReplyRcEx(pClient, RTFTPSERVER_REPLY_PATHNAME_OK, "\"%s\"", szPWD); /* See RFC 959, APPENDIX II. */
1997
1998 return rc;
1999}
2000
2001static DECLCALLBACK(int) rtFtpServerHandleOPTS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2002{
2003 RT_NOREF(cArgs, apszArgs);
2004
2005 int rc = VINF_SUCCESS;
2006
2007 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2008 if (RT_SUCCESS(rc))
2009 rc = rc2;
2010
2011 return rc;
2012}
2013
2014static DECLCALLBACK(int) rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2015{
2016 RT_NOREF(cArgs, apszArgs);
2017
2018 int rc = VINF_SUCCESS;
2019
2020 if (pClient->pDataConn)
2021 {
2022 rc = rtFtpServerDataConnClose(pClient->pDataConn);
2023 if (RT_SUCCESS(rc))
2024 {
2025 rtFtpServerDataConnDestroy(pClient->pDataConn);
2026 pClient->pDataConn = NULL;
2027 }
2028 }
2029
2030 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2031 if (RT_SUCCESS(rc))
2032 rc = rc2;
2033
2034 return rc;
2035}
2036
2037static DECLCALLBACK(int) rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2038{
2039 if (cArgs != 1) /* File name needs to be present. */
2040 return VERR_INVALID_PARAMETER;
2041
2042 int rc;
2043
2044 const char *pszPath = apszArgs[0];
2045
2046 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pszPath, NULL /* PRTFSOBJINFO */);
2047
2048 if (RT_SUCCESS(rc))
2049 {
2050 if (RT_SUCCESS(rc))
2051 {
2052 if (pClient->pDataConn == NULL)
2053 {
2054 rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn);
2055 if (RT_SUCCESS(rc))
2056 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnFileWriteThread, cArgs, apszArgs);
2057
2058 int rc2 = rtFtpServerSendReplyRc( pClient, RT_SUCCESS(rc)
2059 ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN
2060 : RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
2061 AssertRC(rc2);
2062 }
2063 else
2064 {
2065 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN);
2066 AssertRC(rc2);
2067 }
2068 }
2069 else
2070 {
2071 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
2072 AssertRC(rc2);
2073 }
2074 }
2075
2076 if (RT_FAILURE(rc))
2077 {
2078 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2079 AssertRC(rc2);
2080 }
2081
2082 return rc;
2083}
2084
2085static DECLCALLBACK(int) rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2086{
2087 if (cArgs != 1)
2088 return VERR_INVALID_PARAMETER;
2089
2090 int rc;
2091
2092 const char *pszPath = apszArgs[0];
2093 uint64_t uSize = 0;
2094
2095 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pszPath, &uSize);
2096
2097 if (RT_SUCCESS(rc))
2098 {
2099 rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
2100 }
2101 else
2102 {
2103 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2104 AssertRC(rc2);
2105 }
2106
2107 return rc;
2108}
2109
2110static DECLCALLBACK(int) rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2111{
2112 if (cArgs != 1)
2113 return VERR_INVALID_PARAMETER;
2114
2115 int rc;
2116
2117 RTFSOBJINFO objInfo;
2118 RT_ZERO(objInfo);
2119
2120 const char *pszPath = apszArgs[0];
2121
2122 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pszPath, &objInfo);
2123
2124 if (RT_SUCCESS(rc))
2125 {
2126 char szFsObjInfo[_4K]; /** @todo Check this size. */
2127 rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
2128 if (RT_SUCCESS(rc))
2129 {
2130 char szFsPathInfo[RTPATH_MAX + 16];
2131 const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pszPath), pszPath);
2132 if (cchPathInfo > 0)
2133 {
2134 rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
2135 if (RT_SUCCESS(rc))
2136 rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
2137 }
2138 else
2139 rc = VERR_BUFFER_OVERFLOW;
2140 }
2141 }
2142
2143 if (RT_FAILURE(rc))
2144 {
2145 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2146 AssertRC(rc2);
2147 }
2148
2149 return rc;
2150}
2151
2152static DECLCALLBACK(int) rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2153{
2154 if (cArgs != 1)
2155 return VERR_INVALID_PARAMETER;
2156
2157 const char *pszType = apszArgs[0];
2158
2159 int rc;
2160
2161 if (!RTStrICmp(pszType, "F"))
2162 {
2163 pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
2164
2165 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2166 }
2167 else
2168 rc = VERR_NOT_IMPLEMENTED;
2169
2170 return rc;
2171}
2172
2173static DECLCALLBACK(int) rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2174{
2175 RT_NOREF(cArgs, apszArgs);
2176
2177 char szOSInfo[64];
2178 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
2179 if (RT_SUCCESS(rc))
2180 rc = rtFtpServerSendReplyStr(pClient, "215 %s", szOSInfo);
2181
2182 return rc;
2183}
2184
2185static DECLCALLBACK(int) rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2186{
2187 if (cArgs != 1)
2188 return VERR_INVALID_PARAMETER;
2189
2190 const char *pszType = apszArgs[0];
2191
2192 int rc = VINF_SUCCESS;
2193
2194 if (!RTStrICmp(pszType, "A"))
2195 {
2196 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
2197 }
2198 else if (!RTStrICmp(pszType, "I")) /* Image (binary). */
2199 {
2200 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE;
2201 }
2202 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
2203 rc = VERR_NOT_IMPLEMENTED;
2204
2205 if (RT_SUCCESS(rc))
2206 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2207
2208 return rc;
2209}
2210
2211static DECLCALLBACK(int) rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apszArgs)
2212{
2213 if (cArgs != 1)
2214 return VERR_INVALID_PARAMETER;
2215
2216 const char *pszUser = apszArgs[0];
2217 AssertPtrReturn(pszUser, VERR_INVALID_PARAMETER);
2218
2219 rtFtpServerClientStateReset(&pClient->State);
2220
2221 int rc = rtFtpServerLookupUser(pClient, pszUser);
2222 if (RT_SUCCESS(rc))
2223 {
2224 pClient->State.pszUser = RTStrDup(pszUser);
2225 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
2226
2227 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
2228 }
2229 else
2230 {
2231 pClient->State.cFailedLoginAttempts++;
2232
2233 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
2234 if (RT_SUCCESS(rc))
2235 rc = rc2;
2236 }
2237
2238 return rc;
2239}
2240
2241
2242/*********************************************************************************************************************************
2243* Internal server functions *
2244*********************************************************************************************************************************/
2245
2246/**
2247 * Parses FTP command arguments handed in by the client.
2248 *
2249 * @returns VBox status code.
2250 * @param pszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
2251 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
2252 * @param ppapszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
2253 */
2254static int rtFtpServerCmdArgsParse(const char *pszCmdParms, uint8_t *pcArgs, char ***ppapszArgs)
2255{
2256 *pcArgs = 0;
2257 *ppapszArgs = NULL;
2258
2259 if (!pszCmdParms) /* No parms given? Bail out early. */
2260 return VINF_SUCCESS;
2261
2262 /** @todo Anything else to do here? */
2263 /** @todo Check if quoting is correct. */
2264
2265 int cArgs = 0;
2266 int rc = RTGetOptArgvFromString(ppapszArgs, &cArgs, pszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
2267 if (RT_SUCCESS(rc))
2268 {
2269 if ((unsigned int)cArgs <= (unsigned int)UINT8_MAX)
2270 {
2271 *pcArgs = (uint8_t)cArgs;
2272 }
2273 else
2274 rc = VERR_INVALID_PARAMETER;
2275 }
2276
2277 return rc;
2278}
2279
2280/**
2281 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
2282 *
2283 * @param ppapszArgs Argument string array to free.
2284 */
2285static void rtFtpServerCmdArgsFree(char **ppapszArgs)
2286{
2287 RTGetOptArgvFree(ppapszArgs);
2288}
2289
2290/**
2291 * Main function for processing client commands for the control connection.
2292 *
2293 * @returns VBox status code.
2294 * @param pClient Client to process commands for.
2295 * @param pszCmd Command string to parse and handle.
2296 * @param cbCmd Size (in bytes) of command string.
2297 */
2298static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pszCmd, size_t cbCmd)
2299{
2300 /* Make sure to terminate the string in any case. */
2301 pszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0';
2302
2303 /* A tiny bit of sanitation. */
2304 RTStrStripL(pszCmd);
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 char *pszCmdEnd = RTStrStr(pszCmd, "\r\n");
2309 if (pszCmdEnd)
2310 *pszCmdEnd = '\0';
2311
2312 /* Reply which gets sent back to the client. */
2313 RTFTPSERVER_REPLY rcClient = RTFTPSERVER_REPLY_INVALID;
2314
2315 int rcCmd = VINF_SUCCESS;
2316
2317 uint8_t cArgs = 0;
2318 char **papszArgs = NULL;
2319 int rc = rtFtpServerCmdArgsParse(pszCmd, &cArgs, &papszArgs);
2320 if ( RT_SUCCESS(rc)
2321 && cArgs) /* At least the actual command (without args) must be present. */
2322 {
2323 LogFlowFunc(("Handling command '%s'\n", papszArgs[0]));
2324 for (uint8_t a = 0; a < cArgs; a++)
2325 LogFlowFunc(("\targ[%RU8] = '%s'\n", a, papszArgs[a]));
2326
2327 unsigned i = 0;
2328 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
2329 {
2330 const RTFTPSERVERCMD_ENTRY *pCmdEntry = &g_aCmdMap[i];
2331
2332 if (!RTStrICmp(papszArgs[0], pCmdEntry->szCmd))
2333 {
2334 /* Some commands need a valid user before they can be executed. */
2335 if ( pCmdEntry->fNeedsUser
2336 && pClient->State.pszUser == NULL)
2337 {
2338 rcClient = RTFTPSERVER_REPLY_NOT_LOGGED_IN;
2339 break;
2340 }
2341
2342 /* Save timestamp of last command sent. */
2343 pClient->State.tsLastCmdMs = RTTimeMilliTS();
2344
2345 /* Hand in arguments only without the actual command. */
2346 rcCmd = pCmdEntry->pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
2347 if (RT_FAILURE(rcCmd))
2348 {
2349 LogFunc(("Handling command '%s' failed with %Rrc\n", papszArgs[0], rcCmd));
2350
2351 switch (rcCmd)
2352 {
2353 case VERR_INVALID_PARAMETER:
2354 RT_FALL_THROUGH();
2355 case VERR_INVALID_POINTER:
2356 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
2357 break;
2358
2359 case VERR_NOT_IMPLEMENTED:
2360 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
2361 break;
2362
2363 default:
2364 break;
2365 }
2366 }
2367 break;
2368 }
2369 }
2370
2371 rtFtpServerCmdArgsFree(papszArgs);
2372
2373 if (i == RT_ELEMENTS(g_aCmdMap))
2374 {
2375 LogFlowFunc(("Command not implemented\n"));
2376 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
2377 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
2378 }
2379
2380 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVERCMD_QUIT
2381 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
2382 if (fDisconnect)
2383 {
2384 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser);
2385
2386 rtFtpServerClientStateReset(&pClient->State);
2387
2388 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
2389 rcClient = RTFTPSERVER_REPLY_CLOSING_CTRL_CONN;
2390 }
2391 }
2392 else
2393 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
2394
2395 if (rcClient != RTFTPSERVER_REPLY_INVALID)
2396 {
2397 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
2398 if (RT_SUCCESS(rc))
2399 rc = rc2;
2400 }
2401
2402 LogFlowFuncLeaveRC(rc);
2403 return rc;
2404}
2405
2406/**
2407 * Main loop for processing client commands.
2408 *
2409 * @returns VBox status code.
2410 * @param pClient Client to process commands for.
2411 */
2412static int rtFtpServerClientMain(PRTFTPSERVERCLIENT pClient)
2413{
2414 int rc;
2415
2416 size_t cbRead;
2417 char szCmd[RTFTPSERVER_MAX_CMD_LEN + 1];
2418
2419 for (;;)
2420 {
2421 rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */
2422 if (RT_SUCCESS(rc))
2423 {
2424 rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
2425 if ( RT_SUCCESS(rc)
2426 && cbRead)
2427 {
2428 AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW);
2429 /* rc ignored, keep going */ rtFtpServerProcessCommands(pClient, szCmd, cbRead);
2430 }
2431 }
2432 else if (rc == VERR_TIMEOUT)
2433 rc = VINF_SUCCESS;
2434 else
2435 break;
2436
2437 /*
2438 * Handle data connection replies.
2439 */
2440 if (pClient->pDataConn)
2441 {
2442 if ( ASMAtomicReadBool(&pClient->pDataConn->fStarted)
2443 && ASMAtomicReadBool(&pClient->pDataConn->fStopped))
2444 {
2445 Assert(pClient->pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS);
2446
2447 int rc2 = rtFtpServerSendReplyRc(pClient,
2448 RT_SUCCESS(pClient->pDataConn->rc)
2449 ? RTFTPSERVER_REPLY_CLOSING_DATA_CONN : RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
2450 AssertRC(rc2);
2451
2452 rc = rtFtpServerDataConnStop(pClient->pDataConn);
2453 if (RT_SUCCESS(rc))
2454 {
2455 rtFtpServerDataConnDestroy(pClient->pDataConn);
2456 pClient->pDataConn = NULL;
2457 }
2458 }
2459 }
2460 }
2461
2462 /* Make sure to destroy all data connections. */
2463 rtFtpServerDataConnDestroy(pClient->pDataConn);
2464 pClient->pDataConn = NULL;
2465
2466 LogFlowFuncLeaveRC(rc);
2467 return rc;
2468}
2469
2470/**
2471 * Resets the client's state.
2472 *
2473 * @param pState Client state to reset.
2474 */
2475static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
2476{
2477 LogFlowFuncEnter();
2478
2479 RTStrFree(pState->pszUser);
2480 pState->pszUser = NULL;
2481
2482 int rc2 = rtFtpSetCWD(pState, "/");
2483 AssertRC(rc2);
2484
2485 pState->cFailedLoginAttempts = 0;
2486 pState->tsLastCmdMs = RTTimeMilliTS();
2487 pState->enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
2488 pState->enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
2489}
2490
2491/**
2492 * Per-client thread for serving the server's control connection.
2493 *
2494 * @returns VBox status code.
2495 * @param hSocket Socket handle to use for the control connection.
2496 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
2497 */
2498static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
2499{
2500 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
2501 RTFTPSERVER_VALID_RETURN(pThis);
2502
2503 RTFTPSERVERCLIENT Client;
2504 RT_ZERO(Client);
2505
2506 Client.pServer = pThis;
2507 Client.hSocket = hSocket;
2508
2509 LogFlowFunc(("New client connected\n"));
2510
2511 rtFtpServerClientStateReset(&Client.State);
2512
2513 /*
2514 * Send welcome message.
2515 * Note: Some clients (like FileZilla / Firefox) expect a message together with the reply code,
2516 * so make sure to include at least *something*.
2517 */
2518 int rc = rtFtpServerSendReplyRcEx(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER,
2519 "Welcome!");
2520 if (RT_SUCCESS(rc))
2521 {
2522 ASMAtomicIncU32(&pThis->cClients);
2523
2524 rc = rtFtpServerClientMain(&Client);
2525
2526 ASMAtomicDecU32(&pThis->cClients);
2527 }
2528
2529 rtFtpServerClientStateReset(&Client.State);
2530
2531 return rc;
2532}
2533
2534RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pszAddress, uint16_t uPort,
2535 PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
2536{
2537 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
2538 AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
2539 AssertReturn (uPort, VERR_INVALID_PARAMETER);
2540 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
2541 /* pvUser is optional. */
2542
2543 int rc;
2544
2545 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
2546 if (pThis)
2547 {
2548 pThis->u32Magic = RTFTPSERVER_MAGIC;
2549 pThis->Callbacks = *pCallbacks;
2550 pThis->pvUser = pvUser;
2551 pThis->cbUser = cbUser;
2552
2553 rc = RTTcpServerCreate(pszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
2554 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
2555 if (RT_SUCCESS(rc))
2556 {
2557 *phFTPServer = (RTFTPSERVER)pThis;
2558 }
2559 }
2560 else
2561 rc = VERR_NO_MEMORY;
2562
2563 return rc;
2564}
2565
2566RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
2567{
2568 if (hFTPServer == NIL_RTFTPSERVER)
2569 return VINF_SUCCESS;
2570
2571 PRTFTPSERVERINTERNAL pThis = hFTPServer;
2572 RTFTPSERVER_VALID_RETURN(pThis);
2573
2574 AssertPtr(pThis->pTCPServer);
2575
2576 int rc = RTTcpServerDestroy(pThis->pTCPServer);
2577 if (RT_SUCCESS(rc))
2578 {
2579 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
2580
2581 RTMemFree(pThis);
2582 }
2583
2584 return rc;
2585}
2586
Note: See TracBrowser for help on using the repository browser.

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