VirtualBox

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

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

IPRT/FTP: Build fix. bugref:9646

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 81.2 KB
Line 
1/* $Id: ftp-server.cpp 82839 2020-01-23 09:19:03Z 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 /* Make sure there is space in the collection for the new entry. */
1555 if (pCollection->cEntries >= pCollection->cEntriesAllocated)
1556 {
1557 size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16;
1558 void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0]));
1559 if (!pvNew)
1560 return VERR_NO_MEMORY;
1561 pCollection->papEntries = (PPRTFTPDIRENTRY)pvNew;
1562 pCollection->cEntriesAllocated = cNew;
1563 }
1564
1565 /* Create and insert a new entry. */
1566 size_t const cchEntry = strlen(pszEntry);
1567 size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0;
1568 size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0;
1569 size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0;
1570 size_t const cbEntry = RT_UOFFSETOF_DYN(RTFTPDIRENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]);
1571 PRTFTPDIRENTRY pEntry = (PRTFTPDIRENTRY)RTMemAlloc(cbEntry);
1572 if (pEntry)
1573 {
1574 pEntry->Info = *pInfo;
1575 pEntry->pszTarget = NULL; /** @todo symbolic links. */
1576 pEntry->pszOwner = NULL;
1577 pEntry->pszGroup = NULL;
1578 pEntry->cchName = cchEntry;
1579 memcpy(pEntry->szName, pszEntry, cchEntry);
1580 pEntry->szName[cchEntry] = '\0';
1581
1582 char *psz = &pEntry->szName[cchEntry + 1];
1583 if (pszTarget)
1584 {
1585 pEntry->pszTarget = psz;
1586 memcpy(psz, pszTarget, cbTarget);
1587 psz += cbTarget;
1588 }
1589 if (pszOwner)
1590 {
1591 pEntry->pszOwner = psz;
1592 memcpy(psz, pszOwner, cbOwner);
1593 psz += cbOwner;
1594 }
1595 if (pszGroup)
1596 {
1597 pEntry->pszGroup = psz;
1598 memcpy(psz, pszGroup, cbGroup);
1599 }
1600
1601 pCollection->papEntries[pCollection->cEntries++] = pEntry;
1602 pCollection->cbTotalAllocated += pEntry->Info.cbAllocated;
1603 pCollection->cbTotalFiles += pEntry->Info.cbObject;
1604 return VINF_SUCCESS;
1605 }
1606 return VERR_NO_MEMORY;
1607}
1608
1609/** @callback_method_impl{FNRTSORTCMP, Name} */
1610static DECLCALLBACK(int) rtFtpServerCollEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser)
1611{
1612 RT_NOREF(pvUser);
1613 PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1;
1614 PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2;
1615 return RTStrCmp(pEntry1->szName, pEntry2->szName);
1616}
1617
1618/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */
1619static DECLCALLBACK(int) rtFtpServerCollEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser)
1620{
1621 RT_NOREF(pvUser);
1622 PRTFTPDIRENTRY pEntry1 = (PRTFTPDIRENTRY)pvElement1;
1623 PRTFTPDIRENTRY pEntry2 = (PRTFTPDIRENTRY)pvElement2;
1624 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
1625 if (!iDiff)
1626 iDiff = rtFtpServerCollEntryCmpName(pEntry1, pEntry2, pvUser);
1627 return iDiff;
1628}
1629
1630/**
1631 * Sorts a given directory collection according to the FTP server's LIST style.
1632 *
1633 * @param pCollection Collection to sort.
1634 */
1635static void rtFtpServerCollSort(PRTFTPDIRCOLLECTION pCollection)
1636{
1637 PFNRTSORTCMP pfnCmp = rtFtpServerCollEntryCmpDirFirstName;
1638 if (pfnCmp)
1639 RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL);
1640}
1641
1642/**
1643 * Writes a directory collection to a specific data connection.
1644 *
1645 * @returns VBox status code.
1646 * @param pDataConn Data connection to write directory collection to.
1647 * @param pCollection Collection to write.
1648 * @param pszTmp Temporary buffer used for writing.
1649 * @param cbTmp Size (in bytes) of temporary buffer used for writing.
1650 */
1651static int rtFtpServerDataConnDirCollWrite(PRTFTPSERVERDATACONN pDataConn, PRTFTPDIRCOLLECTION pCollection,
1652 char *pszTmp, size_t cbTmp)
1653{
1654 size_t cchSizeCol = 4;
1655 size_t cchLinkCol = 1;
1656 size_t cchUidCol = 1;
1657 size_t cchGidCol = 1;
1658
1659 size_t i = pCollection->cEntries;
1660 while (i-- > 0)
1661 {
1662 PRTFTPDIRENTRY pEntry = pCollection->papEntries[i];
1663
1664 rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp);
1665 size_t cchTmp = strlen(pszTmp);
1666 if (cchTmp > cchSizeCol)
1667 cchSizeCol = cchTmp;
1668
1669 cchTmp = rtFtpServerDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1;
1670 if (cchTmp > cchLinkCol)
1671 cchLinkCol = cchTmp;
1672
1673 rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp);
1674 cchTmp = strlen(pszTmp);
1675 if (cchTmp > cchUidCol)
1676 cchUidCol = cchTmp;
1677
1678 rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp);
1679 cchTmp = strlen(pszTmp);
1680 if (cchTmp > cchGidCol)
1681 cchGidCol = cchTmp;
1682 }
1683
1684 size_t offTime = RT_UOFFSETOF(RTFTPDIRENTRY, Info.ModificationTime);
1685
1686 /*
1687 * Display the entries.
1688 */
1689 for (i = 0; i < pCollection->cEntries; i++)
1690 {
1691 PRTFTPDIRENTRY pEntry = pCollection->papEntries[i];
1692
1693 RTFMODE fMode = pEntry->Info.Attr.fMode;
1694 switch (fMode & RTFS_TYPE_MASK)
1695 {
1696 case RTFS_TYPE_FIFO: rtFtpServerDataConnPrintf(pDataConn, "f"); break;
1697 case RTFS_TYPE_DEV_CHAR: rtFtpServerDataConnPrintf(pDataConn, "c"); break;
1698 case RTFS_TYPE_DIRECTORY: rtFtpServerDataConnPrintf(pDataConn, "d"); break;
1699 case RTFS_TYPE_DEV_BLOCK: rtFtpServerDataConnPrintf(pDataConn, "b"); break;
1700 case RTFS_TYPE_FILE: rtFtpServerDataConnPrintf(pDataConn, "-"); break;
1701 case RTFS_TYPE_SYMLINK: rtFtpServerDataConnPrintf(pDataConn, "l"); break;
1702 case RTFS_TYPE_SOCKET: rtFtpServerDataConnPrintf(pDataConn, "s"); break;
1703 case RTFS_TYPE_WHITEOUT: rtFtpServerDataConnPrintf(pDataConn, "w"); break;
1704 default: rtFtpServerDataConnPrintf(pDataConn, "?"); AssertFailed(); break;
1705 }
1706
1707 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1708 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
1709 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
1710 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
1711 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1712 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
1713 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
1714 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
1715 rtFtpServerDataConnPrintf(pDataConn, "%c%c%c",
1716 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
1717 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
1718 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
1719
1720 rtFtpServerDataConnPrintf(pDataConn, " %*u",
1721 cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks);
1722
1723 if (cchUidCol)
1724 rtFtpServerDataConnPrintf(pDataConn, " %*s", cchUidCol,
1725 rtFtpServerDecimalFormatOwner(pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp));
1726 if (cchGidCol)
1727 rtFtpServerDataConnPrintf(pDataConn, " %*s", cchGidCol,
1728 rtFtpServerDecimalFormatGroup(pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp));
1729
1730 rtFtpServerDataConnPrintf(pDataConn, "%*s", cchSizeCol, rtFtpServerFormatSize(pEntry->Info.cbObject, pszTmp, cbTmp));
1731
1732 PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime);
1733 rtFtpServerDataConnPrintf(pDataConn," %s", rtFtpServerFormatTimestamp(pTime, pszTmp, cbTmp));
1734
1735 rtFtpServerDataConnPrintf(pDataConn," %s\r\n", rtFtpServerFormatName(pEntry->szName, pszTmp, cbTmp));
1736 }
1737
1738 return VINF_SUCCESS;
1739}
1740
1741/**
1742 * Thread for handling the LIST command's output in a separate data connection.
1743 *
1744 * @returns VBox status code.
1745 * @param ThreadSelf Thread handle. Unused.
1746 * @param pvUser User-provided arguments. Of type PRTFTPSERVERCLIENT.
1747 */
1748static DECLCALLBACK(int) rtFtpServerDataConnListThread(RTTHREAD ThreadSelf, void *pvUser)
1749{
1750 RT_NOREF(ThreadSelf);
1751
1752 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
1753 AssertPtr(pClient);
1754
1755 PRTFTPSERVERDATACONN pDataConn = pClient->pDataConn;
1756 AssertPtr(pDataConn);
1757
1758 LogFlowFuncEnter();
1759
1760 int rc;
1761
1762 char szTmp[RTPATH_MAX * 2];
1763 PRTFTPDIRCOLLECTION pColl = rtFtpServerDataConnDirCollAlloc();
1764 AssertPtrReturn(pColl, VERR_NO_MEMORY);
1765
1766 /* Set start indicator. */
1767 pDataConn->fStarted = true;
1768
1769 RTThreadUserSignal(RTThreadSelf());
1770
1771 /* The first argument might indicate a directory to list.
1772 * If no argument is given, the implementation must use the last directory set. */
1773 char *pszPath = RTStrDup( pDataConn->cArgs == 1
1774 ? pDataConn->papszArgs[0] : pDataConn->pClient->State.pszCWD); /** @todo Needs locking. */
1775 AssertPtrReturn(pszPath, VERR_NO_MEMORY);
1776 /* The paths already have been validated in the actual command handlers. */
1777
1778 void *pvHandle = NULL; /* Shut up MSVC. */
1779 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirOpen, pszPath, &pvHandle);
1780
1781 for (;;)
1782 {
1783 RTFSOBJINFO objInfo;
1784 RT_ZERO(objInfo);
1785
1786 char *pszEntry = NULL;
1787 char *pszOwner = NULL;
1788 char *pszGroup = NULL;
1789 char *pszTarget = NULL;
1790
1791 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirRead, pvHandle, &pszEntry,
1792 &objInfo, &pszOwner, &pszGroup, &pszTarget);
1793 if (RT_SUCCESS(rc))
1794 {
1795 int rc2 = rtFtpServerDataConnDirCollAddEntry(pColl, pszEntry,
1796 &objInfo, pszOwner, pszGroup, pszTarget);
1797
1798 RTStrFree(pszEntry);
1799 pszEntry = NULL;
1800
1801 RTStrFree(pszOwner);
1802 pszOwner = NULL;
1803
1804 RTStrFree(pszGroup);
1805 pszGroup = NULL;
1806
1807 RTStrFree(pszTarget);
1808 pszTarget = NULL;
1809
1810 if (RT_SUCCESS(rc))
1811 rc = rc2;
1812 }
1813 else
1814 {
1815 if (rc == VERR_NO_MORE_FILES)
1816 {
1817 rc = VINF_SUCCESS;
1818 break;
1819 }
1820 }
1821
1822 if (RT_FAILURE(rc))
1823 break;
1824
1825 if (ASMAtomicReadBool(&pDataConn->fStop))
1826 break;
1827 }
1828
1829 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnDirClose, pvHandle);
1830 pvHandle = NULL;
1831
1832 rtFtpServerCollSort(pColl);
1833
1834 if (RT_SUCCESS(rc))
1835 {
1836 int rc2 = rtFtpServerDataConnDirCollWrite(pDataConn, pColl, szTmp, sizeof(szTmp));
1837 AssertRC(rc2);
1838 }
1839
1840 rtFtpServerDataConnDirCollFree(pColl);
1841
1842 RTStrFree(pszPath);
1843
1844 pDataConn->fStopped = true;
1845 pDataConn->rc = rc;
1846
1847 LogFlowFuncLeaveRC(rc);
1848 return rc;
1849}
1850
1851static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1852{
1853 /* If no argument is given, use the server's CWD as the path. */
1854 const char *pcszPath = cArgs ? apcszArgs[0] : pClient->State.pszCWD;
1855 AssertPtr(pcszPath);
1856
1857 int rc = VINF_SUCCESS;
1858
1859 if (!rtFtpServerPathIsValid(pcszPath, false /* fIsAbsolute */))
1860 {
1861 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1862 AssertRC(rc2);
1863 }
1864 else
1865 {
1866 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
1867
1868 if (RT_SUCCESS(rc))
1869 {
1870 if (pClient->pDataConn == NULL)
1871 {
1872 rc = rtFtpServerDataConnCreate(pClient, &pClient->pDataConn);
1873 if (RT_SUCCESS(rc))
1874 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnListThread, cArgs, apcszArgs);
1875
1876 int rc2 = rtFtpServerSendReplyRc( pClient, RT_SUCCESS(rc)
1877 ? RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN
1878 : RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
1879 AssertRC(rc2);
1880 }
1881 else
1882 {
1883 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_DATACONN_ALREADY_OPEN);
1884 AssertRC(rc2);
1885 }
1886 }
1887 else
1888 {
1889 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
1890 AssertRC(rc2);
1891 }
1892 }
1893
1894 return rc;
1895}
1896
1897static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1898{
1899 RT_NOREF(pClient, cArgs, apcszArgs);
1900
1901 /** @todo Anything to do here? */
1902 return VINF_SUCCESS;
1903}
1904
1905static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1906{
1907 RT_NOREF(cArgs, apcszArgs);
1908
1909 /* Save timestamp of last command sent. */
1910 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1911
1912 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1913}
1914
1915static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1916{
1917 if (cArgs != 1)
1918 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1919
1920 const char *pcszPassword = apcszArgs[0];
1921 AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
1922
1923 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
1924 if (RT_SUCCESS(rc))
1925 {
1926 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
1927 }
1928 else
1929 {
1930 pClient->State.cFailedLoginAttempts++;
1931
1932 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1933 if (RT_SUCCESS(rc))
1934 rc = rc2;
1935 }
1936
1937 return rc;
1938}
1939
1940static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1941{
1942 if (cArgs != 1)
1943 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1944
1945 RTFTPSERVER_REPLY rcClient;
1946
1947 int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConnAddr, &pClient->uDataConnPort);
1948 if (RT_SUCCESS(rc))
1949 rcClient = RTFTPSERVER_REPLY_OKAY;
1950 else
1951 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
1952
1953 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
1954 if (RT_SUCCESS(rc))
1955 rc = rc2;
1956
1957 return rc;
1958}
1959
1960static int rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1961{
1962 RT_NOREF(cArgs, apcszArgs);
1963
1964 int rc;
1965
1966 char szPWD[RTPATH_MAX];
1967
1968 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
1969
1970 if (RT_SUCCESS(rc))
1971 rc = rtFtpServerSendReplyRcEx(pClient, RTFTPSERVER_REPLY_PATHNAME_OK, "\"%s\"", szPWD); /* See RFC 959, APPENDIX II. */
1972
1973 return rc;
1974}
1975
1976static int rtFtpServerHandleOPTS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1977{
1978 RT_NOREF(cArgs, apcszArgs);
1979
1980 int rc = VINF_SUCCESS;
1981
1982 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1983 if (RT_SUCCESS(rc))
1984 rc = rc2;
1985
1986 return rc;
1987}
1988
1989static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1990{
1991 RT_NOREF(cArgs, apcszArgs);
1992
1993 int rc = VINF_SUCCESS;
1994
1995 if (pClient->pDataConn)
1996 {
1997 rc = rtFtpServerDataConnClose(pClient->pDataConn);
1998 if (RT_SUCCESS(rc))
1999 {
2000 rtFtpServerDataConnDestroy(pClient->pDataConn);
2001 pClient->pDataConn = NULL;
2002 }
2003 }
2004
2005 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2006 if (RT_SUCCESS(rc))
2007 rc = rc2;
2008
2009 return rc;
2010}
2011
2012static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2013{
2014 if (cArgs != 1) /* File name needs to be present. */
2015 return VERR_INVALID_PARAMETER;
2016
2017 int rc;
2018
2019 const char *pcszPath = apcszArgs[0];
2020
2021 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
2022
2023 if (RT_SUCCESS(rc))
2024 {
2025 if (pClient->pDataConn)
2026 {
2027 /* Note: Data connection gets created when the PORT command was sent. */
2028 rc = rtFtpServerDataConnStart(pClient->pDataConn, rtFtpServerDataConnFileWriteThread, cArgs, apcszArgs);
2029 if (RT_SUCCESS(rc))
2030 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_FILE_STS_OK_OPENING_DATA_CONN);
2031 }
2032 else
2033 rc = VERR_FTP_DATA_CONN_NOT_FOUND;
2034 }
2035
2036 if (RT_FAILURE(rc))
2037 {
2038 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2039 AssertRC(rc2);
2040 }
2041
2042 return rc;
2043}
2044
2045static int rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2046{
2047 if (cArgs != 1)
2048 return VERR_INVALID_PARAMETER;
2049
2050 int rc;
2051
2052 const char *pcszPath = apcszArgs[0];
2053 uint64_t uSize = 0;
2054
2055 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pcszPath, &uSize);
2056
2057 if (RT_SUCCESS(rc))
2058 {
2059 rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
2060 }
2061 else
2062 {
2063 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2064 AssertRC(rc2);
2065 }
2066
2067 return rc;
2068}
2069
2070static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2071{
2072 if (cArgs != 1)
2073 return VERR_INVALID_PARAMETER;
2074
2075 int rc;
2076
2077 RTFSOBJINFO objInfo;
2078 RT_ZERO(objInfo);
2079
2080 const char *pcszPath = apcszArgs[0];
2081
2082 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, &objInfo);
2083
2084 if (RT_SUCCESS(rc))
2085 {
2086 char szFsObjInfo[_4K]; /** @todo Check this size. */
2087 rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
2088 if (RT_SUCCESS(rc))
2089 {
2090 char szFsPathInfo[RTPATH_MAX + 16];
2091 const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pcszPath), pcszPath);
2092 if (cchPathInfo > 0)
2093 {
2094 rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
2095 if (RT_SUCCESS(rc))
2096 rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
2097 }
2098 else
2099 rc = VERR_BUFFER_OVERFLOW;
2100 }
2101 }
2102
2103 if (RT_FAILURE(rc))
2104 {
2105 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
2106 AssertRC(rc2);
2107 }
2108
2109 return rc;
2110}
2111
2112static int rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2113{
2114 if (cArgs != 1)
2115 return VERR_INVALID_PARAMETER;
2116
2117 const char *pcszType = apcszArgs[0];
2118
2119 int rc;
2120
2121 if (!RTStrICmp(pcszType, "F"))
2122 {
2123 pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
2124
2125 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2126 }
2127 else
2128 rc = VERR_NOT_IMPLEMENTED;
2129
2130 return rc;
2131}
2132
2133static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2134{
2135 RT_NOREF(cArgs, apcszArgs);
2136
2137 char szOSInfo[64];
2138 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
2139 if (RT_SUCCESS(rc))
2140 rc = rtFtpServerSendReplyStr(pClient, "215 %s", szOSInfo);
2141
2142 return rc;
2143}
2144
2145static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2146{
2147 if (cArgs != 1)
2148 return VERR_INVALID_PARAMETER;
2149
2150 const char *pcszType = apcszArgs[0];
2151
2152 int rc = VINF_SUCCESS;
2153
2154 if (!RTStrICmp(pcszType, "A"))
2155 {
2156 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
2157 }
2158 else if (!RTStrICmp(pcszType, "I")) /* Image (binary). */
2159 {
2160 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE;
2161 }
2162 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
2163 rc = VERR_NOT_IMPLEMENTED;
2164
2165 if (RT_SUCCESS(rc))
2166 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
2167
2168 return rc;
2169}
2170
2171static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
2172{
2173 if (cArgs != 1)
2174 return VERR_INVALID_PARAMETER;
2175
2176 const char *pcszUser = apcszArgs[0];
2177 AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
2178
2179 rtFtpServerClientStateReset(&pClient->State);
2180
2181 int rc = rtFtpServerLookupUser(pClient, pcszUser);
2182 if (RT_SUCCESS(rc))
2183 {
2184 pClient->State.pszUser = RTStrDup(pcszUser);
2185 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
2186
2187 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
2188 }
2189 else
2190 {
2191 pClient->State.cFailedLoginAttempts++;
2192
2193 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
2194 if (RT_SUCCESS(rc))
2195 rc = rc2;
2196 }
2197
2198 return rc;
2199}
2200
2201
2202/*********************************************************************************************************************************
2203* Internal server functions *
2204*********************************************************************************************************************************/
2205
2206/**
2207 * Parses FTP command arguments handed in by the client.
2208 *
2209 * @returns VBox status code.
2210 * @param pcszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
2211 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
2212 * @param ppapcszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
2213 */
2214static int rtFtpServerCmdArgsParse(const char *pcszCmdParms, uint8_t *pcArgs, char ***ppapcszArgs)
2215{
2216 *pcArgs = 0;
2217 *ppapcszArgs = NULL;
2218
2219 if (!pcszCmdParms) /* No parms given? Bail out early. */
2220 return VINF_SUCCESS;
2221
2222 /** @todo Anything else to do here? */
2223 /** @todo Check if quoting is correct. */
2224
2225 int cArgs = 0;
2226 int rc = RTGetOptArgvFromString(ppapcszArgs, &cArgs, pcszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
2227 if (RT_SUCCESS(rc))
2228 {
2229 if (cArgs <= UINT8_MAX)
2230 {
2231 *pcArgs = (uint8_t)cArgs;
2232 }
2233 else
2234 rc = VERR_INVALID_PARAMETER;
2235 }
2236
2237 return rc;
2238}
2239
2240/**
2241 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
2242 *
2243 * @param ppapcszArgs Argument string array to free.
2244 */
2245static void rtFtpServerCmdArgsFree(char **ppapcszArgs)
2246{
2247 RTGetOptArgvFree(ppapcszArgs);
2248}
2249
2250/**
2251 * Main function for processing client commands for the control connection.
2252 *
2253 * @returns VBox status code.
2254 * @param pClient Client to process commands for.
2255 * @param pcszCmd Command string to parse and handle.
2256 * @param cbCmd Size (in bytes) of command string.
2257 */
2258static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pcszCmd, size_t cbCmd)
2259{
2260 /* Make sure to terminate the string in any case. */
2261 pcszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0';
2262
2263 /* A tiny bit of sanitation. */
2264 RTStrStripL(pcszCmd);
2265
2266 /* First, terminate string by finding the command end marker (telnet style). */
2267 /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
2268 char *pszCmdEnd = RTStrIStr(pcszCmd, "\r\n");
2269 if (pszCmdEnd)
2270 *pszCmdEnd = '\0';
2271
2272 /* Reply which gets sent back to the client. */
2273 RTFTPSERVER_REPLY rcClient = RTFTPSERVER_REPLY_INVALID;
2274
2275 int rcCmd = VINF_SUCCESS;
2276
2277 uint8_t cArgs = 0;
2278 char **papszArgs = NULL;
2279 int rc = rtFtpServerCmdArgsParse(pcszCmd, &cArgs, &papszArgs);
2280 if ( RT_SUCCESS(rc)
2281 && cArgs) /* At least the actual command (without args) must be present. */
2282 {
2283 LogFlowFunc(("Handling command '%s'\n", papszArgs[0]));
2284
2285 unsigned i = 0;
2286 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
2287 {
2288 const RTFTPSERVER_CMD_ENTRY *pCmdEntry = &g_aCmdMap[i];
2289
2290 if (!RTStrICmp(papszArgs[0], pCmdEntry->szCmd))
2291 {
2292 /* Some commands need a valid user before they can be executed. */
2293 if ( pCmdEntry->fNeedsUser
2294 && pClient->State.pszUser == NULL)
2295 {
2296 rcClient = RTFTPSERVER_REPLY_NOT_LOGGED_IN;
2297 break;
2298 }
2299
2300 /* Save timestamp of last command sent. */
2301 pClient->State.tsLastCmdMs = RTTimeMilliTS();
2302
2303 /* Hand in arguments only without the actual command. */
2304 rcCmd = pCmdEntry->pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
2305 if (RT_FAILURE(rcCmd))
2306 {
2307 LogFunc(("Handling command '%s' failed with %Rrc\n", papszArgs[0], rcCmd));
2308
2309 switch (rcCmd)
2310 {
2311 case VERR_INVALID_PARAMETER:
2312 RT_FALL_THROUGH();
2313 case VERR_INVALID_POINTER:
2314 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
2315 break;
2316
2317 case VERR_NOT_IMPLEMENTED:
2318 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
2319 break;
2320
2321 default:
2322 break;
2323 }
2324 }
2325 break;
2326 }
2327 }
2328
2329 rtFtpServerCmdArgsFree(papszArgs);
2330
2331 if (i == RT_ELEMENTS(g_aCmdMap))
2332 {
2333 LogFlowFunc(("Command not implemented\n"));
2334 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
2335 rcClient = RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL;
2336 }
2337
2338 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
2339 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
2340 if (fDisconnect)
2341 {
2342 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser);
2343
2344 rtFtpServerClientStateReset(&pClient->State);
2345
2346 Assert(rcClient == RTFTPSERVER_REPLY_INVALID);
2347 rcClient = RTFTPSERVER_REPLY_CLOSING_CTRL_CONN;
2348 }
2349 }
2350 else
2351 rcClient = RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS;
2352
2353 if (rcClient != RTFTPSERVER_REPLY_INVALID)
2354 {
2355 int rc2 = rtFtpServerSendReplyRc(pClient, rcClient);
2356 if (RT_SUCCESS(rc))
2357 rc = rc2;
2358 }
2359
2360 LogFlowFuncLeaveRC(rc);
2361 return rc;
2362}
2363
2364/**
2365 * Main loop for processing client commands.
2366 *
2367 * @returns VBox status code.
2368 * @param pClient Client to process commands for.
2369 */
2370static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
2371{
2372 int rc;
2373
2374 size_t cbRead;
2375 char szCmd[RTFTPSERVER_MAX_CMD_LEN + 1];
2376
2377 for (;;)
2378 {
2379 rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */
2380 if (RT_SUCCESS(rc))
2381 {
2382 rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
2383 if ( RT_SUCCESS(rc)
2384 && cbRead)
2385 {
2386 AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW);
2387 rc = rtFtpServerProcessCommands(pClient, szCmd, cbRead);
2388 }
2389 }
2390 else
2391 {
2392 if (rc == VERR_TIMEOUT)
2393 rc = VINF_SUCCESS;
2394
2395 if (RT_FAILURE(rc))
2396 break;
2397 }
2398
2399 /*
2400 * Handle data connection replies.
2401 */
2402 if (pClient->pDataConn)
2403 {
2404 if ( ASMAtomicReadBool(&pClient->pDataConn->fStarted)
2405 && ASMAtomicReadBool(&pClient->pDataConn->fStopped))
2406 {
2407 Assert(pClient->pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS);
2408
2409 int rc2 = rtFtpServerSendReplyRc(pClient,
2410 RT_SUCCESS(pClient->pDataConn->rc)
2411 ? RTFTPSERVER_REPLY_CLOSING_DATA_CONN : RTFTPSERVER_REPLY_CONN_REQ_FILE_ACTION_NOT_TAKEN);
2412 AssertRC(rc2);
2413
2414 rc = rtFtpServerDataConnStop(pClient->pDataConn);
2415 if (RT_SUCCESS(rc))
2416 {
2417 rtFtpServerDataConnDestroy(pClient->pDataConn);
2418 pClient->pDataConn = NULL;
2419 }
2420 }
2421 }
2422 }
2423
2424 /* Make sure to destroy all data connections. */
2425 rtFtpServerDataConnDestroy(pClient->pDataConn);
2426 pClient->pDataConn = NULL;
2427
2428 LogFlowFuncLeaveRC(rc);
2429 return rc;
2430}
2431
2432/**
2433 * Resets the client's state.
2434 *
2435 * @param pState Client state to reset.
2436 */
2437static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
2438{
2439 LogFlowFuncEnter();
2440
2441 RTStrFree(pState->pszUser);
2442 pState->pszUser = NULL;
2443
2444 int rc2 = rtFtpSetCWD(pState, "/");
2445 AssertRC(rc2);
2446
2447 pState->cFailedLoginAttempts = 0;
2448 pState->tsLastCmdMs = RTTimeMilliTS();
2449 pState->enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
2450 pState->enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
2451}
2452
2453/**
2454 * Per-client thread for serving the server's control connection.
2455 *
2456 * @returns VBox status code.
2457 * @param hSocket Socket handle to use for the control connection.
2458 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
2459 */
2460static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
2461{
2462 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
2463 RTFTPSERVER_VALID_RETURN(pThis);
2464
2465 RTFTPSERVERCLIENT Client;
2466 RT_ZERO(Client);
2467
2468 Client.pServer = pThis;
2469 Client.hSocket = hSocket;
2470
2471 LogFlowFunc(("New client connected\n"));
2472
2473 rtFtpServerClientStateReset(&Client.State);
2474
2475 /*
2476 * Send welcome message.
2477 * Note: Some clients (like FileZilla / Firefox) expect a message together with the reply code,
2478 * so make sure to include at least *something*.
2479 */
2480 int rc = rtFtpServerSendReplyRcEx(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER,
2481 "Welcome!");
2482 if (RT_SUCCESS(rc))
2483 {
2484 ASMAtomicIncU32(&pThis->cClients);
2485
2486 rc = rtFtpServerProcessCommands(&Client);
2487
2488 ASMAtomicDecU32(&pThis->cClients);
2489 }
2490
2491 rtFtpServerClientStateReset(&Client.State);
2492
2493 return rc;
2494}
2495
2496RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
2497 PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
2498{
2499 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
2500 AssertPtrReturn(pcszAddress, VERR_INVALID_POINTER);
2501 AssertReturn (uPort, VERR_INVALID_PARAMETER);
2502 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
2503 /* pvUser is optional. */
2504
2505 int rc;
2506
2507 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
2508 if (pThis)
2509 {
2510 pThis->u32Magic = RTFTPSERVER_MAGIC;
2511 pThis->Callbacks = *pCallbacks;
2512 pThis->pvUser = pvUser;
2513 pThis->cbUser = cbUser;
2514
2515 rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
2516 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
2517 if (RT_SUCCESS(rc))
2518 {
2519 *phFTPServer = (RTFTPSERVER)pThis;
2520 }
2521 }
2522 else
2523 rc = VERR_NO_MEMORY;
2524
2525 return rc;
2526}
2527
2528RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
2529{
2530 if (hFTPServer == NIL_RTFTPSERVER)
2531 return VINF_SUCCESS;
2532
2533 PRTFTPSERVERINTERNAL pThis = hFTPServer;
2534 RTFTPSERVER_VALID_RETURN(pThis);
2535
2536 AssertPtr(pThis->pTCPServer);
2537
2538 int rc = RTTcpServerDestroy(pThis->pTCPServer);
2539 if (RT_SUCCESS(rc))
2540 {
2541 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
2542
2543 RTMemFree(pThis);
2544 }
2545
2546 return rc;
2547}
2548
Note: See TracBrowser for help on using the repository browser.

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