VirtualBox

source: vbox/trunk/src/VBox/Runtime/generic/ftp-server.cpp@ 83487

Last change on this file since 83487 was 82842, checked in by vboxsync, 5 years ago

IPRT/FTP: Retrieving files in sub directories also works now. bugref:9646

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