VirtualBox

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

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

IPRT/FTP: Made replies more compatible with clients which expect additional messages together with the codes. bugref:9646

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 49.0 KB
Line 
1/* $Id: ftp-server.cpp 82737 2020-01-14 13:45:22Z 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 support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++).
33 * - No FTPS / SFTP support.
34 * - No passive mode ("PASV") support.
35 * - No IPv6 support.
36 * - No proxy support.
37 * - No FXP support.
38 */
39
40
41/*********************************************************************************************************************************
42* Header Files *
43*********************************************************************************************************************************/
44#define LOG_GROUP RTLOGGROUP_FTP
45#include <iprt/ftp.h>
46#include "internal/iprt.h"
47#include "internal/magics.h"
48
49#include <iprt/asm.h>
50#include <iprt/assert.h>
51#include <iprt/err.h>
52#include <iprt/file.h> /* For file mode flags. */
53#include <iprt/getopt.h>
54#include <iprt/mem.h>
55#include <iprt/log.h>
56#include <iprt/path.h>
57#include <iprt/poll.h>
58#include <iprt/socket.h>
59#include <iprt/string.h>
60#include <iprt/system.h>
61#include <iprt/tcp.h>
62
63
64/*********************************************************************************************************************************
65* Structures and Typedefs *
66*********************************************************************************************************************************/
67/**
68 * Internal FTP server instance.
69 */
70typedef struct RTFTPSERVERINTERNAL
71{
72 /** Magic value. */
73 uint32_t u32Magic;
74 /** Callback table. */
75 RTFTPSERVERCALLBACKS Callbacks;
76 /** Pointer to TCP server instance. */
77 PRTTCPSERVER pTCPServer;
78 /** Number of currently connected clients. */
79 uint32_t cClients;
80 /** Pointer to user-specific data. Optional. */
81 void *pvUser;
82 /** Size of user-specific data. Optional. */
83 size_t cbUser;
84} RTFTPSERVERINTERNAL;
85/** Pointer to an internal FTP server instance. */
86typedef RTFTPSERVERINTERNAL *PRTFTPSERVERINTERNAL;
87
88
89/*********************************************************************************************************************************
90* Defined Constants And Macros *
91*********************************************************************************************************************************/
92/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
93#define RTFTPSERVER_VALID_RETURN_RC(hFTPServer, a_rc) \
94 do { \
95 AssertPtrReturn((hFTPServer), (a_rc)); \
96 AssertReturn((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC, (a_rc)); \
97 } while (0)
98
99/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
100#define RTFTPSERVER_VALID_RETURN(hFTPServer) RTFTPSERVER_VALID_RETURN_RC((hFTPServer), VERR_INVALID_HANDLE)
101
102/** Validates a handle and returns (void) if not valid. */
103#define RTFTPSERVER_VALID_RETURN_VOID(hFTPServer) \
104 do { \
105 AssertPtrReturnVoid(hFTPServer); \
106 AssertReturnVoid((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC); \
107 } while (0)
108
109/** Supported FTP server command IDs.
110 * Alphabetically, named after their official command names. */
111typedef enum RTFTPSERVER_CMD
112{
113 /** Invalid command, do not use. Always must come first. */
114 RTFTPSERVER_CMD_INVALID = 0,
115 /** Aborts the current command on the server. */
116 RTFTPSERVER_CMD_ABOR,
117 /** Changes the current working directory. */
118 RTFTPSERVER_CMD_CDUP,
119 /** Changes the current working directory. */
120 RTFTPSERVER_CMD_CWD,
121 /** Reports features supported by the server. */
122 RTFTPSERVER_CMD_FEAT,
123 /** Lists a directory. */
124 RTFTPSERVER_CMD_LIST,
125 /** Sets the transfer mode. */
126 RTFTPSERVER_CMD_MODE,
127 /** Sends a nop ("no operation") to the server. */
128 RTFTPSERVER_CMD_NOOP,
129 /** Sets the password for authentication. */
130 RTFTPSERVER_CMD_PASS,
131 /** Sets the port to use for the data connection. */
132 RTFTPSERVER_CMD_PORT,
133 /** Gets the current working directory. */
134 RTFTPSERVER_CMD_PWD,
135 /** Terminates the session (connection). */
136 RTFTPSERVER_CMD_QUIT,
137 /** Retrieves a specific file. */
138 RTFTPSERVER_CMD_RETR,
139 /** Retrieves the size of a file. */
140 RTFTPSERVER_CMD_SIZE,
141 /** Retrieves the current status of a transfer. */
142 RTFTPSERVER_CMD_STAT,
143 /** Sets the structure type to use. */
144 RTFTPSERVER_CMD_STRU,
145 /** Gets the server's OS info. */
146 RTFTPSERVER_CMD_SYST,
147 /** Sets the (data) representation type. */
148 RTFTPSERVER_CMD_TYPE,
149 /** Sets the user name for authentication. */
150 RTFTPSERVER_CMD_USER,
151 /** End marker. */
152 RTFTPSERVER_CMD_LAST,
153 /** The usual 32-bit hack. */
154 RTFTPSERVER_CMD_32BIT_HACK = 0x7fffffff
155} RTFTPSERVER_CMD;
156
157/**
158 * Structure for maintaining a single data connection.
159 */
160typedef struct RTFTPSERVERDATACONN
161{
162 /** Data connection IP. */
163 RTNETADDRIPV4 Addr;
164 /** Data connection port number. */
165 uint16_t uPort;
166 /** The current data socket to use.
167 * Can be NIL_RTSOCKET if no data port has been specified (yet) or has been closed. */
168 RTSOCKET hSocket;
169 /** Thread serving the data connection. */
170 RTTHREAD hThread;
171 /** Thread started indicator. */
172 volatile bool fStarted;
173 /** Thread stop indicator. */
174 volatile bool fStop;
175 /** Thread stopped indicator. */
176 volatile bool fStopped;
177 /** Overall result of data connection on stop. */
178 int rc;
179 /** For now we only support sending a single file per active data connection. */
180 char szFile[RTPATH_MAX];
181} RTFTPSERVERDATACONN;
182/** Pointer to a data connection struct. */
183typedef RTFTPSERVERDATACONN *PRTFTPSERVERDATACONN;
184
185/**
186 * Structure for maintaining an internal FTP server client.
187 */
188typedef struct RTFTPSERVERCLIENT
189{
190 /** Pointer to internal server state. */
191 PRTFTPSERVERINTERNAL pServer;
192 /** Socket handle the client is bound to. */
193 RTSOCKET hSocket;
194 /** Actual client state. */
195 RTFTPSERVERCLIENTSTATE State;
196 /** Data connection information.
197 * At the moment we only allow one data connection per client at a time. */
198 RTFTPSERVERDATACONN DataConn;
199} RTFTPSERVERCLIENT;
200/** Pointer to an internal FTP server client state. */
201typedef RTFTPSERVERCLIENT *PRTFTPSERVERCLIENT;
202
203/** Function pointer declaration for a specific FTP server command handler. */
204typedef DECLCALLBACK(int) FNRTFTPSERVERCMD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs);
205/** Pointer to a FNRTFTPSERVERCMD(). */
206typedef FNRTFTPSERVERCMD *PFNRTFTPSERVERCMD;
207
208/** Handles a FTP server callback with no arguments and returns. */
209#define RTFTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
210 do \
211 { \
212 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
213 if (pCallbacks->a_Name) \
214 { \
215 RTFTPCALLBACKDATA Data = { &pClient->State }; \
216 return pCallbacks->a_Name(&Data); \
217 } \
218 else \
219 return VERR_NOT_IMPLEMENTED; \
220 } while (0)
221
222/** Handles a FTP server callback with no arguments and sets rc accordingly. */
223#define RTFTPSERVER_HANDLE_CALLBACK(a_Name) \
224 do \
225 { \
226 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
227 if (pCallbacks->a_Name) \
228 { \
229 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
230 rc = pCallbacks->a_Name(&Data); \
231 } \
232 else \
233 rc = VERR_NOT_IMPLEMENTED; \
234 } while (0)
235
236/** Handles a FTP server callback with arguments and sets rc accordingly. */
237#define RTFTPSERVER_HANDLE_CALLBACK_VA(a_Name, ...) \
238 do \
239 { \
240 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
241 if (pCallbacks->a_Name) \
242 { \
243 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
244 rc = pCallbacks->a_Name(&Data, __VA_ARGS__); \
245 } \
246 else \
247 rc = VERR_NOT_IMPLEMENTED; \
248 } while (0)
249
250/** Handles a FTP server callback with arguments and returns. */
251#define RTFTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
252 do \
253 { \
254 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
255 if (pCallbacks->a_Name) \
256 { \
257 RTFTPCALLBACKDATA Data = { &pClient->State, pClient->pServer->pvUser, pClient->pServer->cbUser }; \
258 return pCallbacks->a_Name(&Data, __VA_ARGS__); \
259 } \
260 else \
261 return VERR_NOT_IMPLEMENTED; \
262 } while (0)
263
264
265/*********************************************************************************************************************************
266* Defined Constants And Macros *
267*********************************************************************************************************************************/
268
269static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn);
270static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort);
271static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState);
272
273/**
274 * Function prototypes for command handlers.
275 */
276static FNRTFTPSERVERCMD rtFtpServerHandleABOR;
277static FNRTFTPSERVERCMD rtFtpServerHandleCDUP;
278static FNRTFTPSERVERCMD rtFtpServerHandleCWD;
279static FNRTFTPSERVERCMD rtFtpServerHandleFEAT;
280static FNRTFTPSERVERCMD rtFtpServerHandleLIST;
281static FNRTFTPSERVERCMD rtFtpServerHandleMODE;
282static FNRTFTPSERVERCMD rtFtpServerHandleNOOP;
283static FNRTFTPSERVERCMD rtFtpServerHandlePASS;
284static FNRTFTPSERVERCMD rtFtpServerHandlePORT;
285static FNRTFTPSERVERCMD rtFtpServerHandlePWD;
286static FNRTFTPSERVERCMD rtFtpServerHandleQUIT;
287static FNRTFTPSERVERCMD rtFtpServerHandleRETR;
288static FNRTFTPSERVERCMD rtFtpServerHandleSIZE;
289static FNRTFTPSERVERCMD rtFtpServerHandleSTAT;
290static FNRTFTPSERVERCMD rtFtpServerHandleSTRU;
291static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
292static FNRTFTPSERVERCMD rtFtpServerHandleTYPE;
293static FNRTFTPSERVERCMD rtFtpServerHandleUSER;
294
295
296/**
297 * Structure for maintaining a single command entry for the command table.
298 */
299typedef struct RTFTPSERVER_CMD_ENTRY
300{
301 /** Command ID. */
302 RTFTPSERVER_CMD enmCmd;
303 /** Command represented as ASCII string. */
304 char szCmd[RTFTPSERVER_MAX_CMD_LEN];
305 /** Function pointer invoked to handle the command. */
306 PFNRTFTPSERVERCMD pfnCmd;
307} RTFTPSERVER_CMD_ENTRY;
308
309/**
310 * Table of handled commands.
311 */
312const RTFTPSERVER_CMD_ENTRY g_aCmdMap[] =
313{
314 { RTFTPSERVER_CMD_ABOR, "ABOR", rtFtpServerHandleABOR },
315 { RTFTPSERVER_CMD_CDUP, "CDUP", rtFtpServerHandleCDUP },
316 { RTFTPSERVER_CMD_CWD, "CWD", rtFtpServerHandleCWD },
317 { RTFTPSERVER_CMD_FEAT, "FEAT", rtFtpServerHandleFEAT },
318 { RTFTPSERVER_CMD_LIST, "LIST", rtFtpServerHandleLIST },
319 { RTFTPSERVER_CMD_MODE, "MODE", rtFtpServerHandleMODE },
320 { RTFTPSERVER_CMD_NOOP, "NOOP", rtFtpServerHandleNOOP },
321 { RTFTPSERVER_CMD_PASS, "PASS", rtFtpServerHandlePASS },
322 { RTFTPSERVER_CMD_PORT, "PORT", rtFtpServerHandlePORT },
323 { RTFTPSERVER_CMD_PWD, "PWD", rtFtpServerHandlePWD },
324 { RTFTPSERVER_CMD_QUIT, "QUIT", rtFtpServerHandleQUIT },
325 { RTFTPSERVER_CMD_RETR, "RETR", rtFtpServerHandleRETR },
326 { RTFTPSERVER_CMD_SIZE, "SIZE", rtFtpServerHandleSIZE },
327 { RTFTPSERVER_CMD_STAT, "STAT", rtFtpServerHandleSTAT },
328 { RTFTPSERVER_CMD_STRU, "STRU", rtFtpServerHandleSTRU },
329 { RTFTPSERVER_CMD_SYST, "SYST", rtFtpServerHandleSYST },
330 { RTFTPSERVER_CMD_TYPE, "TYPE", rtFtpServerHandleTYPE },
331 { RTFTPSERVER_CMD_USER, "USER", rtFtpServerHandleUSER },
332 { RTFTPSERVER_CMD_LAST, "", NULL }
333};
334
335/** Feature string which represents all commands we support in addition to RFC 959.
336 * Must match the command table above.
337 *
338 * Don't forget the terminating ";" at each feature. */
339#define RTFTPSERVER_FEATURES_STRING \
340 "SIZE;" /* Supports reporting file sizes. */
341
342
343/*********************************************************************************************************************************
344* Protocol Functions *
345*********************************************************************************************************************************/
346
347/**
348 * Replies a (three digit) reply code back to the client.
349 *
350 * @returns VBox status code.
351 * @param pClient Client to reply to.
352 * @param enmReply Reply code to send.
353 */
354static int rtFtpServerSendReplyRc(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply)
355{
356 /* Note: If we don't supply any additional text, make sure to include an empty stub, as
357 * some clients expect this as part of their parsing code. */
358 char szReply[32];
359 RTStrPrintf2(szReply, sizeof(szReply), "%RU32 -\r\n", enmReply);
360
361 LogFlowFunc(("Sending reply code %RU32\n", enmReply));
362
363 return RTTcpWrite(pClient->hSocket, szReply, strlen(szReply) + 1);
364}
365
366/**
367 * Replies a (three digit) reply code with a custom message back to the client.
368 *
369 * @returns VBox status code.
370 * @param pClient Client to reply to.
371 * @param enmReply Reply code to send.
372 * @param pcszFormat Format string of message to send with the reply code.
373 */
374static int rtFtpServerSendReplyRcEx(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply,
375 const char *pcszFormat, ...)
376{
377 char *pszMsg = NULL;
378
379 va_list args;
380 va_start(args, pcszFormat);
381 char *pszFmt = NULL;
382 const int cch = RTStrAPrintfV(&pszFmt, pcszFormat, args);
383 va_end(args);
384 AssertReturn(cch > 0, VERR_NO_MEMORY);
385
386 int rc = RTStrAPrintf(&pszMsg, "%RU32", enmReply);
387 AssertRCReturn(rc, rc);
388
389 if (pszFmt)
390 {
391 rc = RTStrAAppend(&pszMsg, " - ");
392 AssertRCReturn(rc, rc);
393
394 rc = RTStrAAppend(&pszMsg, pszFmt);
395 AssertRCReturn(rc, rc);
396 }
397
398
399 rc = RTStrAAppend(&pszMsg, "\r\n");
400 AssertRCReturn(rc, rc);
401
402 RTStrFree(pszFmt);
403
404 rc = RTTcpWrite(pClient->hSocket, pszMsg, strlen(pszMsg) + 1 /* Include termination */);
405
406 RTStrFree(pszMsg);
407
408 return rc;
409}
410
411/**
412 * Replies a string back to the client.
413 *
414 * @returns VBox status code.
415 * @param pClient Client to reply to.
416 * @param pcszFormat Format to reply.
417 * @param ... Format arguments.
418 */
419static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszFormat, ...)
420{
421 va_list args;
422 va_start(args, pcszFormat);
423 char *psz = NULL;
424 const int cch = RTStrAPrintfV(&psz, pcszFormat, args);
425 va_end(args);
426 AssertReturn(cch > 0, VERR_NO_MEMORY);
427
428 int rc = RTStrAAppend(&psz, "\r\n");
429 AssertRCReturn(rc, rc);
430
431 rc = RTTcpWrite(pClient->hSocket, psz, strlen(psz) + 1 /* Include termination */);
432
433 RTStrFree(psz);
434
435 return rc;
436}
437
438/**
439 * Looks up an user account.
440 *
441 * @returns VBox status code, or VERR_NOT_FOUND if user has not been found.
442 * @param pClient Client to look up user for.
443 * @param pcszUser User name to look up.
444 */
445static int rtFtpServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pcszUser)
446{
447 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pcszUser);
448}
449
450/**
451 * Handles the actual client authentication.
452 *
453 * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed.
454 * @param pClient Client to authenticate.
455 * @param pcszUser User name to authenticate with.
456 * @param pcszPassword Password to authenticate with.
457 */
458static int rtFtpServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pcszUser, const char *pcszPassword)
459{
460 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pcszUser, pcszPassword);
461}
462
463/**
464 * Converts a RTFSOBJINFO struct to a string.
465 *
466 * @returns VBox status code.
467 * @param pObjInfo RTFSOBJINFO object to convert.
468 * @param pszFsObjInfo Where to store the output string.
469 * @param cbFsObjInfo Size of the output string in bytes.
470 */
471static int rtFtpServerFsObjInfoToStr(PRTFSOBJINFO pObjInfo, char *pszFsObjInfo, size_t cbFsObjInfo)
472{
473 RTFMODE fMode = pObjInfo->Attr.fMode;
474 char chFileType;
475 switch (fMode & RTFS_TYPE_MASK)
476 {
477 case RTFS_TYPE_FIFO: chFileType = 'f'; break;
478 case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
479 case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
480 case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
481 case RTFS_TYPE_FILE: chFileType = '-'; break;
482 case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
483 case RTFS_TYPE_SOCKET: chFileType = 's'; break;
484 case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
485 default: chFileType = '?'; break;
486 }
487
488 char szTimeBirth[RTTIME_STR_LEN];
489 char szTimeChange[RTTIME_STR_LEN];
490 char szTimeModification[RTTIME_STR_LEN];
491 char szTimeAccess[RTTIME_STR_LEN];
492
493#define INFO_TO_STR(a_Format, ...) \
494 do \
495 { \
496 const ssize_t cchSize = RTStrPrintf2(szTemp, sizeof(szTemp), a_Format, __VA_ARGS__); \
497 AssertReturn(cchSize > 0, VERR_BUFFER_OVERFLOW); \
498 const int rc2 = RTStrCat(pszFsObjInfo, cbFsObjInfo, szTemp); \
499 AssertRCReturn(rc2, rc2); \
500 } while (0);
501
502 char szTemp[32];
503
504 INFO_TO_STR("%c", chFileType);
505 INFO_TO_STR("%c%c%c",
506 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
507 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
508 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
509 INFO_TO_STR("%c%c%c",
510 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
511 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
512 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
513 INFO_TO_STR("%c%c%c",
514 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
515 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
516 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
517
518 INFO_TO_STR( " %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
519 fMode & RTFS_DOS_READONLY ? 'R' : '-',
520 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
521 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
522 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
523 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
524 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
525 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
526 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
527 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
528 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
529 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
530 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
531 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
532 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
533
534 INFO_TO_STR( " %d %4d %4d %10lld %10lld",
535 pObjInfo->Attr.u.Unix.cHardlinks,
536 pObjInfo->Attr.u.Unix.uid,
537 pObjInfo->Attr.u.Unix.gid,
538 pObjInfo->cbObject,
539 pObjInfo->cbAllocated);
540
541 INFO_TO_STR( " %s %s %s %s",
542 RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
543 RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
544 RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
545 RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
546
547#undef INFO_TO_STR
548
549 return VINF_SUCCESS;
550}
551
552/**
553 * Parses a string which consists of an IPv4 (ww,xx,yy,zz) and a port number (hi,lo), all separated by comma delimiters.
554 * See RFC 959, 4.1.2.
555 *
556 * @returns VBox status code.
557 * @param pcszStr String to parse.
558 * @param pAddr Where to store the IPv4 address on success.
559 * @param puPort Where to store the port number on success.
560 */
561static int rtFtpParseHostAndPort(const char *pcszStr, PRTNETADDRIPV4 pAddr, uint16_t *puPort)
562{
563 AssertPtrReturn(pcszStr, VERR_INVALID_POINTER);
564 AssertPtrReturn(pAddr, VERR_INVALID_POINTER);
565 AssertPtrReturn(puPort, VERR_INVALID_POINTER);
566
567 char *pszNext;
568 int rc;
569
570 /* Parse IP (v4). */
571 /** @todo I don't think IPv6 ever will be a thing here, or will it? */
572 rc = RTStrToUInt8Ex(pcszStr, &pszNext, 10, &pAddr->au8[0]);
573 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
574 return VERR_INVALID_PARAMETER;
575 if (*pszNext++ != ',')
576 return VERR_INVALID_PARAMETER;
577
578 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]);
579 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
580 return VERR_INVALID_PARAMETER;
581 if (*pszNext++ != ',')
582 return VERR_INVALID_PARAMETER;
583
584 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]);
585 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
586 return VERR_INVALID_PARAMETER;
587 if (*pszNext++ != ',')
588 return VERR_INVALID_PARAMETER;
589
590 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]);
591 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
592 return VERR_INVALID_PARAMETER;
593 if (*pszNext++ != ',')
594 return VERR_INVALID_PARAMETER;
595
596 /* Parse port. */
597 uint8_t uPortHi;
598 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortHi);
599 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
600 return VERR_INVALID_PARAMETER;
601 if (*pszNext++ != ',')
602 return VERR_INVALID_PARAMETER;
603 uint8_t uPortLo;
604 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortLo);
605 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
606 return VERR_INVALID_PARAMETER;
607
608 *puPort = RT_MAKE_U16(uPortLo, uPortHi);
609
610 return rc;
611}
612
613/**
614 * Opens a data connection to the client.
615 *
616 * @returns VBox status code.
617 * @param pDataConn Data connection to open.
618 * @param pAddr Address for the data connection.
619 * @param uPort Port for the data connection.
620 */
621static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort)
622{
623 LogFlowFuncEnter();
624
625 /** @todo Implement IPv6 handling here. */
626 char szAddress[32];
627 const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8",
628 pAddr->au8[0], pAddr->au8[1], pAddr->au8[2], pAddr->au8[3]);
629 AssertReturn(cchAdddress > 0, VERR_NO_MEMORY);
630
631 return RTTcpClientConnect(szAddress, uPort, &pDataConn->hSocket);
632}
633
634/**
635 * Closes a data connection to the client.
636 *
637 * @returns VBox status code.
638 * @param pDataConn Data connection to close.
639 */
640static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn)
641{
642 int rc = VINF_SUCCESS;
643
644 if (pDataConn->hSocket != NIL_RTSOCKET)
645 {
646 LogFlowFuncEnter();
647
648 rc = RTTcpFlush(pDataConn->hSocket);
649 if (RT_SUCCESS(rc))
650 {
651 rc = RTTcpClientClose(pDataConn->hSocket);
652 pDataConn->hSocket = NIL_RTSOCKET;
653 }
654 }
655
656 return rc;
657}
658
659/**
660 * Writes data to the data connection.
661 *
662 * @returns VBox status code.
663 * @param pDataConn Data connection to write to.
664 * @param pvData Data to write.
665 * @param cbData Size (in bytes) of data to write.
666 * @param pcbWritten How many bytes were written. Optional and unused atm.
667 */
668static int rtFtpServerDataConnWrite(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData, size_t *pcbWritten)
669{
670 RT_NOREF(pcbWritten);
671
672 return RTTcpWrite(pDataConn->hSocket, pvData, cbData);
673}
674
675/**
676 * Thread serving a data connection.
677 *
678 * @returns VBox status code.
679 * @param ThreadSelf Thread handle. Unused at the moment.
680 * @param pvUser Pointer to user-provided data. Of type PRTFTPSERVERCLIENT.
681 */
682static DECLCALLBACK(int) rtFtpServerDataConnThread(RTTHREAD ThreadSelf, void *pvUser)
683{
684 RT_NOREF(ThreadSelf);
685
686 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
687 AssertPtr(pClient);
688
689 PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn;
690
691 LogFlowFuncEnter();
692
693 int rc = rtFtpServerDataConnOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort);
694 if (RT_FAILURE(rc))
695 return rc;
696
697 uint32_t cbBuf = _64K; /** @todo Improve this. */
698 void *pvBuf = RTMemAlloc(cbBuf);
699 if (!pvBuf)
700 return VERR_NO_MEMORY;
701
702 /* Set start indicator. */
703 pDataConn->fStarted = true;
704
705 RTThreadUserSignal(RTThreadSelf());
706
707 const char *pcszFile = pDataConn->szFile;
708
709 void *pvHandle = NULL; /* Opaque handle known to the actual implementation. */
710
711 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileOpen, pcszFile,
712 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pvHandle);
713 if (RT_SUCCESS(rc))
714 {
715 LogFlowFunc(("Transfer started\n"));
716
717 do
718 {
719 size_t cbRead = 0;
720 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead);
721 if ( RT_SUCCESS(rc)
722 && cbRead)
723 {
724 rc = rtFtpServerDataConnWrite(pDataConn, pvBuf, cbRead, NULL /* pcbWritten */);
725 }
726
727 if ( !cbRead
728 || ASMAtomicReadBool(&pDataConn->fStop))
729 {
730 break;
731 }
732 }
733 while (RT_SUCCESS(rc));
734
735 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle);
736
737 LogFlowFunc(("Transfer done\n"));
738 }
739
740 rtFtpServerDataConnClose(pDataConn);
741
742 RTMemFree(pvBuf);
743 pvBuf = NULL;
744
745 pDataConn->fStopped = true;
746 pDataConn->rc = rc;
747
748 LogFlowFuncLeaveRC(rc);
749 return rc;
750}
751
752/**
753 * Opens a data connection to the client.
754 *
755 * @returns VBox status code.
756 * @param pClient Client to open data connection for.
757 * @param pDataConn Data connection to open.
758 */
759static int rtFtpServerDataConnCreate(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn)
760{
761 int rc = RTThreadCreate(&pDataConn->hThread, rtFtpServerDataConnThread,
762 pClient, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
763 "ftpdata");
764 if (RT_SUCCESS(rc))
765 {
766 int rc2 = RTThreadUserWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */);
767 AssertRC(rc2);
768
769 if (!pDataConn->fStarted) /* Did the thread indicate that it started correctly? */
770 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
771 }
772
773 return rc;
774}
775
776/**
777 * Closes a data connection to the client.
778 *
779 * @returns VBox status code.
780 * @param pClient Client to close data connection for.
781 * @param pDataConn Data connection to close.
782 */
783static int rtFtpServerDataConnDestroy(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn)
784{
785 RT_NOREF(pClient);
786
787 if (pDataConn->hThread == NIL_RTTHREAD)
788 return VINF_SUCCESS;
789
790 LogFlowFuncEnter();
791
792 /* Set stop indicator. */
793 pDataConn->fStop = true;
794
795 int rcThread = VERR_WRONG_ORDER;
796 int rc = RTThreadWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */, &rcThread);
797 if (RT_SUCCESS(rc))
798 {
799 rtFtpServerDataConnClose(pDataConn);
800 rtFtpServerDataConnReset(pDataConn);
801
802 rc = rcThread;
803 }
804
805 return rc;
806}
807
808/**
809 * Resets a data connection structure.
810 *
811 * @returns VBox status code.
812 * @param pDataConn Data connection structure to reset.
813 */
814static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn)
815{
816 LogFlowFuncEnter();
817
818 pDataConn->hSocket = NIL_RTSOCKET;
819 pDataConn->uPort = 20; /* Default port to use. */
820 pDataConn->hThread = NIL_RTTHREAD;
821 pDataConn->fStarted = false;
822 pDataConn->fStop = false;
823 pDataConn->fStopped = false;
824 pDataConn->rc = VERR_IPE_UNINITIALIZED_STATUS;
825}
826
827
828/*********************************************************************************************************************************
829* Command Protocol Handlers *
830*********************************************************************************************************************************/
831
832static int rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
833{
834 RT_NOREF(cArgs, apcszArgs);
835
836 int rc = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
837 if (RT_SUCCESS(rc))
838 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
839
840 return rc;
841}
842
843static int rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
844{
845 RT_NOREF(cArgs, apcszArgs);
846
847 int rc;
848
849 RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp);
850
851 if (RT_SUCCESS(rc))
852 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
853
854 return rc;
855}
856
857static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
858{
859 if (cArgs != 1)
860 return VERR_INVALID_PARAMETER;
861
862 int rc;
863
864 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, apcszArgs[0]);
865
866 if (RT_SUCCESS(rc))
867 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
868
869 return rc;
870}
871
872static int rtFtpServerHandleFEAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
873{
874 RT_NOREF(cArgs, apcszArgs);
875
876 int rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_SYSTEM_STATUS); /* Features begin */
877 if (RT_SUCCESS(rc))
878 {
879 rc = rtFtpServerSendReplyStr(pClient, RTFTPSERVER_FEATURES_STRING);
880 if (RT_SUCCESS(rc))
881 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_SYSTEM_STATUS); /* Features end */
882 }
883
884 return rc;
885}
886
887static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
888{
889 RT_NOREF(cArgs, apcszArgs);
890
891 int rc;
892
893 void *pvData = NULL;
894 size_t cbData = 0;
895
896 /* The first argument might indicate a directory to list. */
897 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnList,
898 cArgs == 1
899 ? apcszArgs[0] : NULL, &pvData, &cbData);
900
901 if (RT_SUCCESS(rc))
902 {
903 RTMemFree(pvData);
904 }
905
906 return rc;
907}
908
909static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
910{
911 RT_NOREF(pClient, cArgs, apcszArgs);
912
913 /** @todo Anything to do here? */
914 return VINF_SUCCESS;
915}
916
917static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
918{
919 RT_NOREF(cArgs, apcszArgs);
920
921 /* Save timestamp of last command sent. */
922 pClient->State.tsLastCmdMs = RTTimeMilliTS();
923
924 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
925}
926
927static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
928{
929 if (cArgs != 1)
930 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
931
932 const char *pcszPassword = apcszArgs[0];
933 AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
934
935 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
936 if (RT_SUCCESS(rc))
937 {
938 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
939 }
940 else
941 {
942 pClient->State.cFailedLoginAttempts++;
943
944 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
945 if (RT_SUCCESS(rc))
946 rc = rc2;
947 }
948
949 return rc;
950}
951
952static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
953{
954 if (cArgs != 1)
955 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
956
957 /* Only allow one data connection per client at a time. */
958 rtFtpServerDataConnClose(&pClient->DataConn);
959
960 int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConn.Addr, &pClient->DataConn.uPort);
961 if (RT_SUCCESS(rc))
962 {
963 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
964 }
965 else
966 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
967
968 return rc;
969}
970
971static int rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
972{
973 RT_NOREF(cArgs, apcszArgs);
974
975 int rc;
976
977 char szPWD[RTPATH_MAX];
978
979 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
980
981 if (RT_SUCCESS(rc))
982 rc = rtFtpServerSendReplyStr(pClient, szPWD);
983
984 return rc;
985}
986
987static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
988{
989 RT_NOREF(cArgs, apcszArgs);
990
991 return rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
992}
993
994static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
995{
996 if (cArgs != 1)
997 return VERR_INVALID_PARAMETER;
998
999 int rc;
1000
1001 const char *pcszPath = apcszArgs[0];
1002
1003 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
1004
1005 if (RT_SUCCESS(rc))
1006 {
1007 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_FILE_STATUS_OKAY);
1008 if (RT_SUCCESS(rc))
1009 {
1010 rc = RTStrCopy(pClient->DataConn.szFile, sizeof(pClient->DataConn.szFile), pcszPath);
1011 if (RT_SUCCESS(rc))
1012 {
1013 rc = rtFtpServerDataConnCreate(pClient, &pClient->DataConn);
1014 }
1015 }
1016 }
1017
1018 if (RT_FAILURE(rc))
1019 {
1020 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1021 AssertRC(rc2);
1022 }
1023
1024 return rc;
1025}
1026
1027static int rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1028{
1029 if (cArgs != 1)
1030 return VERR_INVALID_PARAMETER;
1031
1032 int rc;
1033
1034 const char *pcszPath = apcszArgs[0];
1035 uint64_t uSize = 0;
1036
1037 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pcszPath, &uSize);
1038
1039 if (RT_SUCCESS(rc))
1040 {
1041 rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
1042 }
1043 else
1044 {
1045 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1046 AssertRC(rc2);
1047 }
1048
1049 return rc;
1050}
1051
1052static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1053{
1054 if (cArgs != 1)
1055 return VERR_INVALID_PARAMETER;
1056
1057 int rc;
1058
1059 RTFSOBJINFO objInfo;
1060 RT_ZERO(objInfo);
1061
1062 const char *pcszPath = apcszArgs[0];
1063
1064 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, &objInfo);
1065
1066 if (RT_SUCCESS(rc))
1067 {
1068 char szFsObjInfo[_4K]; /** @todo Check this size. */
1069 rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
1070 if (RT_SUCCESS(rc))
1071 {
1072 char szFsPathInfo[RTPATH_MAX + 16];
1073 const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pcszPath), pcszPath);
1074 if (cchPathInfo > 0)
1075 {
1076 rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
1077 if (RT_SUCCESS(rc))
1078 rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
1079 }
1080 else
1081 rc = VERR_BUFFER_OVERFLOW;
1082 }
1083 }
1084
1085 if (RT_FAILURE(rc))
1086 {
1087 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1088 AssertRC(rc2);
1089 }
1090
1091 return rc;
1092}
1093
1094static int rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1095{
1096 if (cArgs != 1)
1097 return VERR_INVALID_PARAMETER;
1098
1099 const char *pcszType = apcszArgs[0];
1100
1101 int rc;
1102
1103 if (!RTStrICmp(pcszType, "F"))
1104 {
1105 pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
1106
1107 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1108 }
1109 else
1110 rc = VERR_NOT_IMPLEMENTED;
1111
1112 return rc;
1113}
1114
1115static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1116{
1117 RT_NOREF(cArgs, apcszArgs);
1118
1119 char szOSInfo[64];
1120 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
1121 if (RT_SUCCESS(rc))
1122 rc = rtFtpServerSendReplyStr(pClient, szOSInfo);
1123
1124 return rc;
1125}
1126
1127static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1128{
1129 if (cArgs != 1)
1130 return VERR_INVALID_PARAMETER;
1131
1132 const char *pcszType = apcszArgs[0];
1133
1134 int rc = VINF_SUCCESS;
1135
1136 if (!RTStrICmp(pcszType, "A"))
1137 {
1138 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
1139 }
1140 else if (!RTStrICmp(pcszType, "I")) /* Image (binary). */
1141 {
1142 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE;
1143 }
1144 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
1145 rc = VERR_NOT_IMPLEMENTED;
1146
1147 if (RT_SUCCESS(rc))
1148 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1149
1150 return rc;
1151}
1152
1153static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1154{
1155 if (cArgs != 1)
1156 return VERR_INVALID_PARAMETER;
1157
1158 const char *pcszUser = apcszArgs[0];
1159 AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
1160
1161 rtFtpServerClientStateReset(&pClient->State);
1162
1163 int rc = rtFtpServerLookupUser(pClient, pcszUser);
1164 if (RT_SUCCESS(rc))
1165 {
1166 pClient->State.pszUser = RTStrDup(pcszUser);
1167 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
1168
1169 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
1170 }
1171 else
1172 {
1173 pClient->State.cFailedLoginAttempts++;
1174
1175 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1176 if (RT_SUCCESS(rc))
1177 rc = rc2;
1178 }
1179
1180 return rc;
1181}
1182
1183
1184/*********************************************************************************************************************************
1185* Internal server functions *
1186*********************************************************************************************************************************/
1187
1188/**
1189 * Parses FTP command arguments handed in by the client.
1190 *
1191 * @returns VBox status code.
1192 * @param pcszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
1193 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
1194 * @param ppapcszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
1195 */
1196static int rtFtpServerCmdArgsParse(const char *pcszCmdParms, uint8_t *pcArgs, char ***ppapcszArgs)
1197{
1198 *pcArgs = 0;
1199 *ppapcszArgs = NULL;
1200
1201 if (!pcszCmdParms) /* No parms given? Bail out early. */
1202 return VINF_SUCCESS;
1203
1204 /** @todo Anything else to do here? */
1205 /** @todo Check if quoting is correct. */
1206
1207 int cArgs = 0;
1208 int rc = RTGetOptArgvFromString(ppapcszArgs, &cArgs, pcszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
1209 if (RT_SUCCESS(rc))
1210 {
1211 if (cArgs <= UINT8_MAX)
1212 {
1213 *pcArgs = (uint8_t)cArgs;
1214 }
1215 else
1216 rc = VERR_INVALID_PARAMETER;
1217 }
1218
1219 return rc;
1220}
1221
1222/**
1223 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
1224 *
1225 * @param ppapcszArgs Argument string array to free.
1226 */
1227static void rtFtpServerCmdArgsFree(char **ppapcszArgs)
1228{
1229 RTGetOptArgvFree(ppapcszArgs);
1230}
1231
1232/**
1233 * Main function for processing client commands for the control connection.
1234 *
1235 * @returns VBox status code.
1236 * @param pClient Client to process commands for.
1237 * @param pcszCmd Command string to parse and handle.
1238 * @param cbCmd Size (in bytes) of command string.
1239 */
1240static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pcszCmd, size_t cbCmd)
1241{
1242 /* Make sure to terminate the string in any case. */
1243 pcszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0';
1244
1245 /* A tiny bit of sanitation. */
1246 RTStrStripL(pcszCmd);
1247
1248 /* First, terminate string by finding the command end marker (telnet style). */
1249 /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
1250 char *pszCmdEnd = RTStrIStr(pcszCmd, "\r\n");
1251 if (pszCmdEnd)
1252 *pszCmdEnd = '\0';
1253
1254 int rcCmd = VINF_SUCCESS;
1255
1256 uint8_t cArgs = 0;
1257 char **papszArgs = NULL;
1258 int rc = rtFtpServerCmdArgsParse(pcszCmd, &cArgs, &papszArgs);
1259 if ( RT_SUCCESS(rc)
1260 && cArgs) /* At least the actual command (without args) must be present. */
1261 {
1262 LogFlowFunc(("Handling command '%s'\n", papszArgs[0]));
1263
1264 unsigned i = 0;
1265 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
1266 {
1267 if (!RTStrICmp(papszArgs[0], g_aCmdMap[i].szCmd))
1268 {
1269 /* Save timestamp of last command sent. */
1270 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1271
1272 rcCmd = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
1273 break;
1274 }
1275 }
1276
1277 rtFtpServerCmdArgsFree(papszArgs);
1278
1279 if (i == RT_ELEMENTS(g_aCmdMap))
1280 {
1281 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
1282 if (RT_SUCCESS(rc))
1283 rc = rc2;
1284
1285 LogFlowFunc(("Command not implemented\n"));
1286 return rc;
1287 }
1288
1289 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
1290 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
1291 if (fDisconnect)
1292 {
1293 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CLOSING_CTRL_CONN);
1294 if (RT_SUCCESS(rc))
1295 rc = rc2;
1296
1297 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser);
1298 return rc;
1299 }
1300
1301 switch (rcCmd)
1302 {
1303 case VERR_INVALID_PARAMETER:
1304 RT_FALL_THROUGH();
1305 case VERR_INVALID_POINTER:
1306 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1307 break;
1308
1309 case VERR_NOT_IMPLEMENTED:
1310 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
1311 break;
1312
1313 default:
1314 break;
1315 }
1316 }
1317 else
1318 {
1319 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1320 if (RT_SUCCESS(rc))
1321 rc = rc2;
1322 }
1323
1324 LogFlowFuncLeaveRC(rc);
1325 return rc;
1326}
1327
1328/**
1329 * Main loop for processing client commands.
1330 *
1331 * @returns VBox status code.
1332 * @param pClient Client to process commands for.
1333 */
1334static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
1335{
1336 int rc;
1337
1338 size_t cbRead;
1339 char szCmd[RTFTPSERVER_MAX_CMD_LEN + 1];
1340
1341 for (;;)
1342 {
1343 rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */
1344 if (RT_SUCCESS(rc))
1345 {
1346 rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
1347 if ( RT_SUCCESS(rc)
1348 && cbRead)
1349 {
1350 AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW);
1351 rc = rtFtpServerProcessCommands(pClient, szCmd, cbRead);
1352 }
1353 }
1354 else
1355 {
1356 if (rc == VERR_TIMEOUT)
1357 rc = VINF_SUCCESS;
1358
1359 if (RT_FAILURE(rc))
1360 break;
1361
1362 PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn;
1363
1364 if ( ASMAtomicReadBool(&pDataConn->fStarted)
1365 && ASMAtomicReadBool(&pDataConn->fStopped))
1366 {
1367 Assert(pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS);
1368
1369 rc = rtFtpServerSendReplyRc(pClient, RT_SUCCESS(pDataConn->rc)
1370 ? RTFTPSERVER_REPLY_CLOSING_DATA_CONN
1371 : RTFTPSERVER_REPLY_CONN_CLOSED_TRANSFER_ABORTED);
1372
1373 int rc2 = rtFtpServerDataConnDestroy(pClient, pDataConn);
1374 if (RT_SUCCESS(rc))
1375 rc = rc2;
1376 }
1377 }
1378 }
1379
1380 /* Make sure to close any open data connections. */
1381 int rc2 = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
1382 if (RT_SUCCESS(rc))
1383 rc = rc2;
1384
1385 LogFlowFuncLeaveRC(rc);
1386 return rc;
1387}
1388
1389/**
1390 * Resets the client's state.
1391 *
1392 * @param pState Client state to reset.
1393 */
1394static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
1395{
1396 LogFlowFuncEnter();
1397
1398 RTStrFree(pState->pszUser);
1399 pState->pszUser = NULL;
1400
1401 pState->cFailedLoginAttempts = 0;
1402 pState->tsLastCmdMs = RTTimeMilliTS();
1403 pState->enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
1404 pState->enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
1405}
1406
1407/**
1408 * Per-client thread for serving the server's control connection.
1409 *
1410 * @returns VBox status code.
1411 * @param hSocket Socket handle to use for the control connection.
1412 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
1413 */
1414static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
1415{
1416 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
1417 RTFTPSERVER_VALID_RETURN(pThis);
1418
1419 RTFTPSERVERCLIENT Client;
1420 RT_ZERO(Client);
1421
1422 Client.pServer = pThis;
1423 Client.hSocket = hSocket;
1424
1425 LogFlowFunc(("New client connected\n"));
1426
1427 rtFtpServerClientStateReset(&Client.State);
1428
1429 /*
1430 * Send welcome message.
1431 * Note: Some clients (like FileZilla / Firefox) expect a message together with the reply code,
1432 * so make sure to include at least *something*.
1433 */
1434 int rc = rtFtpServerSendReplyRcEx(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER,
1435 "Welcome!");
1436 if (RT_SUCCESS(rc))
1437 {
1438 ASMAtomicIncU32(&pThis->cClients);
1439
1440 rc = rtFtpServerProcessCommands(&Client);
1441
1442 ASMAtomicDecU32(&pThis->cClients);
1443 }
1444
1445 rtFtpServerClientStateReset(&Client.State);
1446
1447 return rc;
1448}
1449
1450RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
1451 PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
1452{
1453 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
1454 AssertPtrReturn(pcszAddress, VERR_INVALID_POINTER);
1455 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1456 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1457 /* pvUser is optional. */
1458
1459 int rc;
1460
1461 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
1462 if (pThis)
1463 {
1464 pThis->u32Magic = RTFTPSERVER_MAGIC;
1465 pThis->Callbacks = *pCallbacks;
1466 pThis->pvUser = pvUser;
1467 pThis->cbUser = cbUser;
1468
1469 rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
1470 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1471 if (RT_SUCCESS(rc))
1472 {
1473 *phFTPServer = (RTFTPSERVER)pThis;
1474 }
1475 }
1476 else
1477 rc = VERR_NO_MEMORY;
1478
1479 return rc;
1480}
1481
1482RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
1483{
1484 if (hFTPServer == NIL_RTFTPSERVER)
1485 return VINF_SUCCESS;
1486
1487 PRTFTPSERVERINTERNAL pThis = hFTPServer;
1488 RTFTPSERVER_VALID_RETURN(pThis);
1489
1490 AssertPtr(pThis->pTCPServer);
1491
1492 int rc = RTTcpServerDestroy(pThis->pTCPServer);
1493 if (RT_SUCCESS(rc))
1494 {
1495 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
1496
1497 RTMemFree(pThis);
1498 }
1499
1500 return rc;
1501}
1502
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