VirtualBox

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

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