VirtualBox

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

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

IPRT/FTP: Made connecting w/ other clients more compatible by also supplying a (short) welcome message text. bugref:9646

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 48.8 KB
Line 
1/* $Id: ftp-server.cpp 82735 2020-01-14 12:59:53Z 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 char szReply[32];
357 RTStrPrintf2(szReply, sizeof(szReply), "%RU32\r\n", enmReply);
358
359 LogFlowFunc(("Sending reply code %RU32\n", enmReply));
360
361 return RTTcpWrite(pClient->hSocket, szReply, strlen(szReply) + 1);
362}
363
364/**
365 * Replies a (three digit) reply code with a custom message back to the client.
366 *
367 * @returns VBox status code.
368 * @param pClient Client to reply to.
369 * @param enmReply Reply code to send.
370 * @param pcszFormat Format string of message to send with the reply code.
371 */
372static int rtFtpServerSendReplyRcEx(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply,
373 const char *pcszFormat, ...)
374{
375 char *pszMsg = NULL;
376
377 va_list args;
378 va_start(args, pcszFormat);
379 char *pszFmt = NULL;
380 const int cch = RTStrAPrintfV(&pszFmt, pcszFormat, args);
381 va_end(args);
382 AssertReturn(cch > 0, VERR_NO_MEMORY);
383
384 int rc = RTStrAPrintf(&pszMsg, "%RU32", enmReply);
385 AssertRCReturn(rc, rc);
386
387 if (pszFmt)
388 {
389 rc = RTStrAAppend(&pszMsg, " - ");
390 AssertRCReturn(rc, rc);
391
392 rc = RTStrAAppend(&pszMsg, pszFmt);
393 AssertRCReturn(rc, rc);
394 }
395
396
397 rc = RTStrAAppend(&pszMsg, "\r\n");
398 AssertRCReturn(rc, rc);
399
400 RTStrFree(pszFmt);
401
402 rc = RTTcpWrite(pClient->hSocket, pszMsg, strlen(pszMsg) + 1 /* Include termination */);
403
404 RTStrFree(pszMsg);
405
406 return rc;
407}
408
409/**
410 * Replies a string back to the client.
411 *
412 * @returns VBox status code.
413 * @param pClient Client to reply to.
414 * @param pcszFormat Format to reply.
415 * @param ... Format arguments.
416 */
417static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszFormat, ...)
418{
419 va_list args;
420 va_start(args, pcszFormat);
421 char *psz = NULL;
422 const int cch = RTStrAPrintfV(&psz, pcszFormat, args);
423 va_end(args);
424 AssertReturn(cch > 0, VERR_NO_MEMORY);
425
426 int rc = RTStrAAppend(&psz, "\r\n");
427 AssertRCReturn(rc, rc);
428
429 rc = RTTcpWrite(pClient->hSocket, psz, strlen(psz) + 1 /* Include termination */);
430
431 RTStrFree(psz);
432
433 return rc;
434}
435
436/**
437 * Looks up an user account.
438 *
439 * @returns VBox status code, or VERR_NOT_FOUND if user has not been found.
440 * @param pClient Client to look up user for.
441 * @param pcszUser User name to look up.
442 */
443static int rtFtpServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pcszUser)
444{
445 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pcszUser);
446}
447
448/**
449 * Handles the actual client authentication.
450 *
451 * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed.
452 * @param pClient Client to authenticate.
453 * @param pcszUser User name to authenticate with.
454 * @param pcszPassword Password to authenticate with.
455 */
456static int rtFtpServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pcszUser, const char *pcszPassword)
457{
458 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pcszUser, pcszPassword);
459}
460
461/**
462 * Converts a RTFSOBJINFO struct to a string.
463 *
464 * @returns VBox status code.
465 * @param pObjInfo RTFSOBJINFO object to convert.
466 * @param pszFsObjInfo Where to store the output string.
467 * @param cbFsObjInfo Size of the output string in bytes.
468 */
469static int rtFtpServerFsObjInfoToStr(PRTFSOBJINFO pObjInfo, char *pszFsObjInfo, size_t cbFsObjInfo)
470{
471 RTFMODE fMode = pObjInfo->Attr.fMode;
472 char chFileType;
473 switch (fMode & RTFS_TYPE_MASK)
474 {
475 case RTFS_TYPE_FIFO: chFileType = 'f'; break;
476 case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
477 case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
478 case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
479 case RTFS_TYPE_FILE: chFileType = '-'; break;
480 case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
481 case RTFS_TYPE_SOCKET: chFileType = 's'; break;
482 case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
483 default: chFileType = '?'; break;
484 }
485
486 char szTimeBirth[RTTIME_STR_LEN];
487 char szTimeChange[RTTIME_STR_LEN];
488 char szTimeModification[RTTIME_STR_LEN];
489 char szTimeAccess[RTTIME_STR_LEN];
490
491#define INFO_TO_STR(a_Format, ...) \
492 do \
493 { \
494 const ssize_t cchSize = RTStrPrintf2(szTemp, sizeof(szTemp), a_Format, __VA_ARGS__); \
495 AssertReturn(cchSize > 0, VERR_BUFFER_OVERFLOW); \
496 const int rc2 = RTStrCat(pszFsObjInfo, cbFsObjInfo, szTemp); \
497 AssertRCReturn(rc2, rc2); \
498 } while (0);
499
500 char szTemp[32];
501
502 INFO_TO_STR("%c", chFileType);
503 INFO_TO_STR("%c%c%c",
504 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
505 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
506 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
507 INFO_TO_STR("%c%c%c",
508 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
509 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
510 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
511 INFO_TO_STR("%c%c%c",
512 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
513 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
514 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
515
516 INFO_TO_STR( " %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
517 fMode & RTFS_DOS_READONLY ? 'R' : '-',
518 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
519 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
520 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
521 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
522 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
523 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
524 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
525 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
526 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
527 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
528 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
529 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
530 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
531
532 INFO_TO_STR( " %d %4d %4d %10lld %10lld",
533 pObjInfo->Attr.u.Unix.cHardlinks,
534 pObjInfo->Attr.u.Unix.uid,
535 pObjInfo->Attr.u.Unix.gid,
536 pObjInfo->cbObject,
537 pObjInfo->cbAllocated);
538
539 INFO_TO_STR( " %s %s %s %s",
540 RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
541 RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
542 RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
543 RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
544
545#undef INFO_TO_STR
546
547 return VINF_SUCCESS;
548}
549
550/**
551 * Parses a string which consists of an IPv4 (ww,xx,yy,zz) and a port number (hi,lo), all separated by comma delimiters.
552 * See RFC 959, 4.1.2.
553 *
554 * @returns VBox status code.
555 * @param pcszStr String to parse.
556 * @param pAddr Where to store the IPv4 address on success.
557 * @param puPort Where to store the port number on success.
558 */
559static int rtFtpParseHostAndPort(const char *pcszStr, PRTNETADDRIPV4 pAddr, uint16_t *puPort)
560{
561 AssertPtrReturn(pcszStr, VERR_INVALID_POINTER);
562 AssertPtrReturn(pAddr, VERR_INVALID_POINTER);
563 AssertPtrReturn(puPort, VERR_INVALID_POINTER);
564
565 char *pszNext;
566 int rc;
567
568 /* Parse IP (v4). */
569 /** @todo I don't think IPv6 ever will be a thing here, or will it? */
570 rc = RTStrToUInt8Ex(pcszStr, &pszNext, 10, &pAddr->au8[0]);
571 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
572 return VERR_INVALID_PARAMETER;
573 if (*pszNext++ != ',')
574 return VERR_INVALID_PARAMETER;
575
576 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]);
577 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
578 return VERR_INVALID_PARAMETER;
579 if (*pszNext++ != ',')
580 return VERR_INVALID_PARAMETER;
581
582 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]);
583 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
584 return VERR_INVALID_PARAMETER;
585 if (*pszNext++ != ',')
586 return VERR_INVALID_PARAMETER;
587
588 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]);
589 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
590 return VERR_INVALID_PARAMETER;
591 if (*pszNext++ != ',')
592 return VERR_INVALID_PARAMETER;
593
594 /* Parse port. */
595 uint8_t uPortHi;
596 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortHi);
597 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
598 return VERR_INVALID_PARAMETER;
599 if (*pszNext++ != ',')
600 return VERR_INVALID_PARAMETER;
601 uint8_t uPortLo;
602 rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &uPortLo);
603 if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
604 return VERR_INVALID_PARAMETER;
605
606 *puPort = RT_MAKE_U16(uPortLo, uPortHi);
607
608 return rc;
609}
610
611/**
612 * Opens a data connection to the client.
613 *
614 * @returns VBox status code.
615 * @param pDataConn Data connection to open.
616 * @param pAddr Address for the data connection.
617 * @param uPort Port for the data connection.
618 */
619static int rtFtpServerDataConnOpen(PRTFTPSERVERDATACONN pDataConn, PRTNETADDRIPV4 pAddr, uint16_t uPort)
620{
621 LogFlowFuncEnter();
622
623 /** @todo Implement IPv6 handling here. */
624 char szAddress[32];
625 const ssize_t cchAdddress = RTStrPrintf2(szAddress, sizeof(szAddress), "%RU8.%RU8.%RU8.%RU8",
626 pAddr->au8[0], pAddr->au8[1], pAddr->au8[2], pAddr->au8[3]);
627 AssertReturn(cchAdddress > 0, VERR_NO_MEMORY);
628
629 return RTTcpClientConnect(szAddress, uPort, &pDataConn->hSocket);
630}
631
632/**
633 * Closes a data connection to the client.
634 *
635 * @returns VBox status code.
636 * @param pDataConn Data connection to close.
637 */
638static int rtFtpServerDataConnClose(PRTFTPSERVERDATACONN pDataConn)
639{
640 int rc = VINF_SUCCESS;
641
642 if (pDataConn->hSocket != NIL_RTSOCKET)
643 {
644 LogFlowFuncEnter();
645
646 rc = RTTcpFlush(pDataConn->hSocket);
647 if (RT_SUCCESS(rc))
648 {
649 rc = RTTcpClientClose(pDataConn->hSocket);
650 pDataConn->hSocket = NIL_RTSOCKET;
651 }
652 }
653
654 return rc;
655}
656
657/**
658 * Writes data to the data connection.
659 *
660 * @returns VBox status code.
661 * @param pDataConn Data connection to write to.
662 * @param pvData Data to write.
663 * @param cbData Size (in bytes) of data to write.
664 * @param pcbWritten How many bytes were written. Optional and unused atm.
665 */
666static int rtFtpServerDataConnWrite(PRTFTPSERVERDATACONN pDataConn, const void *pvData, size_t cbData, size_t *pcbWritten)
667{
668 RT_NOREF(pcbWritten);
669
670 return RTTcpWrite(pDataConn->hSocket, pvData, cbData);
671}
672
673/**
674 * Thread serving a data connection.
675 *
676 * @returns VBox status code.
677 * @param ThreadSelf Thread handle. Unused at the moment.
678 * @param pvUser Pointer to user-provided data. Of type PRTFTPSERVERCLIENT.
679 */
680static DECLCALLBACK(int) rtFtpServerDataConnThread(RTTHREAD ThreadSelf, void *pvUser)
681{
682 RT_NOREF(ThreadSelf);
683
684 PRTFTPSERVERCLIENT pClient = (PRTFTPSERVERCLIENT)pvUser;
685 AssertPtr(pClient);
686
687 PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn;
688
689 LogFlowFuncEnter();
690
691 int rc = rtFtpServerDataConnOpen(pDataConn, &pDataConn->Addr, pDataConn->uPort);
692 if (RT_FAILURE(rc))
693 return rc;
694
695 uint32_t cbBuf = _64K; /** @todo Improve this. */
696 void *pvBuf = RTMemAlloc(cbBuf);
697 if (!pvBuf)
698 return VERR_NO_MEMORY;
699
700 /* Set start indicator. */
701 pDataConn->fStarted = true;
702
703 RTThreadUserSignal(RTThreadSelf());
704
705 const char *pcszFile = pDataConn->szFile;
706
707 void *pvHandle = NULL; /* Opaque handle known to the actual implementation. */
708
709 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileOpen, pcszFile,
710 RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pvHandle);
711 if (RT_SUCCESS(rc))
712 {
713 LogFlowFunc(("Transfer started\n"));
714
715 do
716 {
717 size_t cbRead = 0;
718 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileRead, pvHandle, pvBuf, cbBuf, &cbRead);
719 if ( RT_SUCCESS(rc)
720 && cbRead)
721 {
722 rc = rtFtpServerDataConnWrite(pDataConn, pvBuf, cbRead, NULL /* pcbWritten */);
723 }
724
725 if ( !cbRead
726 || ASMAtomicReadBool(&pDataConn->fStop))
727 {
728 break;
729 }
730 }
731 while (RT_SUCCESS(rc));
732
733 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileClose, pvHandle);
734
735 LogFlowFunc(("Transfer done\n"));
736 }
737
738 rtFtpServerDataConnClose(pDataConn);
739
740 RTMemFree(pvBuf);
741 pvBuf = NULL;
742
743 pDataConn->fStopped = true;
744 pDataConn->rc = rc;
745
746 LogFlowFuncLeaveRC(rc);
747 return rc;
748}
749
750/**
751 * Opens a data connection to the client.
752 *
753 * @returns VBox status code.
754 * @param pClient Client to open data connection for.
755 * @param pDataConn Data connection to open.
756 */
757static int rtFtpServerDataConnCreate(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn)
758{
759 int rc = RTThreadCreate(&pDataConn->hThread, rtFtpServerDataConnThread,
760 pClient, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
761 "ftpdata");
762 if (RT_SUCCESS(rc))
763 {
764 int rc2 = RTThreadUserWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */);
765 AssertRC(rc2);
766
767 if (!pDataConn->fStarted) /* Did the thread indicate that it started correctly? */
768 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
769 }
770
771 return rc;
772}
773
774/**
775 * Closes a data connection to the client.
776 *
777 * @returns VBox status code.
778 * @param pClient Client to close data connection for.
779 * @param pDataConn Data connection to close.
780 */
781static int rtFtpServerDataConnDestroy(PRTFTPSERVERCLIENT pClient, PRTFTPSERVERDATACONN pDataConn)
782{
783 RT_NOREF(pClient);
784
785 if (pDataConn->hThread == NIL_RTTHREAD)
786 return VINF_SUCCESS;
787
788 LogFlowFuncEnter();
789
790 /* Set stop indicator. */
791 pDataConn->fStop = true;
792
793 int rcThread = VERR_WRONG_ORDER;
794 int rc = RTThreadWait(pDataConn->hThread, 30 * 1000 /* Timeout in ms */, &rcThread);
795 if (RT_SUCCESS(rc))
796 {
797 rtFtpServerDataConnClose(pDataConn);
798 rtFtpServerDataConnReset(pDataConn);
799
800 rc = rcThread;
801 }
802
803 return rc;
804}
805
806/**
807 * Resets a data connection structure.
808 *
809 * @returns VBox status code.
810 * @param pDataConn Data connection structure to reset.
811 */
812static void rtFtpServerDataConnReset(PRTFTPSERVERDATACONN pDataConn)
813{
814 LogFlowFuncEnter();
815
816 pDataConn->hSocket = NIL_RTSOCKET;
817 pDataConn->uPort = 20; /* Default port to use. */
818 pDataConn->hThread = NIL_RTTHREAD;
819 pDataConn->fStarted = false;
820 pDataConn->fStop = false;
821 pDataConn->fStopped = false;
822 pDataConn->rc = VERR_IPE_UNINITIALIZED_STATUS;
823}
824
825
826/*********************************************************************************************************************************
827* Command Protocol Handlers *
828*********************************************************************************************************************************/
829
830static int rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
831{
832 RT_NOREF(cArgs, apcszArgs);
833
834 int rc = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
835 if (RT_SUCCESS(rc))
836 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
837
838 return rc;
839}
840
841static int rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
842{
843 RT_NOREF(cArgs, apcszArgs);
844
845 int rc;
846
847 RTFTPSERVER_HANDLE_CALLBACK(pfnOnPathUp);
848
849 if (RT_SUCCESS(rc))
850 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
851
852 return rc;
853}
854
855static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
856{
857 if (cArgs != 1)
858 return VERR_INVALID_PARAMETER;
859
860 int rc;
861
862 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathSetCurrent, apcszArgs[0]);
863
864 if (RT_SUCCESS(rc))
865 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
866
867 return rc;
868}
869
870static int rtFtpServerHandleFEAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
871{
872 RT_NOREF(cArgs, apcszArgs);
873
874 int rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_SYSTEM_STATUS); /* Features begin */
875 if (RT_SUCCESS(rc))
876 {
877 rc = rtFtpServerSendReplyStr(pClient, RTFTPSERVER_FEATURES_STRING);
878 if (RT_SUCCESS(rc))
879 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_SYSTEM_STATUS); /* Features end */
880 }
881
882 return rc;
883}
884
885static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
886{
887 RT_NOREF(cArgs, apcszArgs);
888
889 int rc;
890
891 void *pvData = NULL;
892 size_t cbData = 0;
893
894 /* The first argument might indicate a directory to list. */
895 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnList,
896 cArgs == 1
897 ? apcszArgs[0] : NULL, &pvData, &cbData);
898
899 if (RT_SUCCESS(rc))
900 {
901 RTMemFree(pvData);
902 }
903
904 return rc;
905}
906
907static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
908{
909 RT_NOREF(pClient, cArgs, apcszArgs);
910
911 /** @todo Anything to do here? */
912 return VINF_SUCCESS;
913}
914
915static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
916{
917 RT_NOREF(cArgs, apcszArgs);
918
919 /* Save timestamp of last command sent. */
920 pClient->State.tsLastCmdMs = RTTimeMilliTS();
921
922 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
923}
924
925static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
926{
927 if (cArgs != 1)
928 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
929
930 const char *pcszPassword = apcszArgs[0];
931 AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
932
933 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
934 if (RT_SUCCESS(rc))
935 {
936 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
937 }
938 else
939 {
940 pClient->State.cFailedLoginAttempts++;
941
942 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
943 if (RT_SUCCESS(rc))
944 rc = rc2;
945 }
946
947 return rc;
948}
949
950static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
951{
952 if (cArgs != 1)
953 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
954
955 /* Only allow one data connection per client at a time. */
956 rtFtpServerDataConnClose(&pClient->DataConn);
957
958 int rc = rtFtpParseHostAndPort(apcszArgs[0], &pClient->DataConn.Addr, &pClient->DataConn.uPort);
959 if (RT_SUCCESS(rc))
960 {
961 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
962 }
963 else
964 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CANT_OPEN_DATA_CONN);
965
966 return rc;
967}
968
969static int rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
970{
971 RT_NOREF(cArgs, apcszArgs);
972
973 int rc;
974
975 char szPWD[RTPATH_MAX];
976
977 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnPathGetCurrent, szPWD, sizeof(szPWD));
978
979 if (RT_SUCCESS(rc))
980 rc = rtFtpServerSendReplyStr(pClient, szPWD);
981
982 return rc;
983}
984
985static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
986{
987 RT_NOREF(cArgs, apcszArgs);
988
989 return rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
990}
991
992static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
993{
994 if (cArgs != 1)
995 return VERR_INVALID_PARAMETER;
996
997 int rc;
998
999 const char *pcszPath = apcszArgs[0];
1000
1001 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, NULL /* PRTFSOBJINFO */);
1002
1003 if (RT_SUCCESS(rc))
1004 {
1005 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_FILE_STATUS_OKAY);
1006 if (RT_SUCCESS(rc))
1007 {
1008 rc = RTStrCopy(pClient->DataConn.szFile, sizeof(pClient->DataConn.szFile), pcszPath);
1009 if (RT_SUCCESS(rc))
1010 {
1011 rc = rtFtpServerDataConnCreate(pClient, &pClient->DataConn);
1012 }
1013 }
1014 }
1015
1016 if (RT_FAILURE(rc))
1017 {
1018 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1019 AssertRC(rc2);
1020 }
1021
1022 return rc;
1023}
1024
1025static int rtFtpServerHandleSIZE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1026{
1027 if (cArgs != 1)
1028 return VERR_INVALID_PARAMETER;
1029
1030 int rc;
1031
1032 const char *pcszPath = apcszArgs[0];
1033 uint64_t uSize = 0;
1034
1035 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileGetSize, pcszPath, &uSize);
1036
1037 if (RT_SUCCESS(rc))
1038 {
1039 rc = rtFtpServerSendReplyStr(pClient, "213 %RU64\r\n", uSize);
1040 }
1041 else
1042 {
1043 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1044 AssertRC(rc2);
1045 }
1046
1047 return rc;
1048}
1049
1050static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1051{
1052 if (cArgs != 1)
1053 return VERR_INVALID_PARAMETER;
1054
1055 int rc;
1056
1057 RTFSOBJINFO objInfo;
1058 RT_ZERO(objInfo);
1059
1060 const char *pcszPath = apcszArgs[0];
1061
1062 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnFileStat, pcszPath, &objInfo);
1063
1064 if (RT_SUCCESS(rc))
1065 {
1066 char szFsObjInfo[_4K]; /** @todo Check this size. */
1067 rc = rtFtpServerFsObjInfoToStr(&objInfo, szFsObjInfo, sizeof(szFsObjInfo));
1068 if (RT_SUCCESS(rc))
1069 {
1070 char szFsPathInfo[RTPATH_MAX + 16];
1071 const ssize_t cchPathInfo = RTStrPrintf2(szFsPathInfo, sizeof(szFsPathInfo), " %2zu %s\n", strlen(pcszPath), pcszPath);
1072 if (cchPathInfo > 0)
1073 {
1074 rc = RTStrCat(szFsObjInfo, sizeof(szFsObjInfo), szFsPathInfo);
1075 if (RT_SUCCESS(rc))
1076 rc = rtFtpServerSendReplyStr(pClient, szFsObjInfo);
1077 }
1078 else
1079 rc = VERR_BUFFER_OVERFLOW;
1080 }
1081 }
1082
1083 if (RT_FAILURE(rc))
1084 {
1085 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_REQ_ACTION_NOT_TAKEN);
1086 AssertRC(rc2);
1087 }
1088
1089 return rc;
1090}
1091
1092static int rtFtpServerHandleSTRU(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1093{
1094 if (cArgs != 1)
1095 return VERR_INVALID_PARAMETER;
1096
1097 const char *pcszType = apcszArgs[0];
1098
1099 int rc;
1100
1101 if (!RTStrICmp(pcszType, "F"))
1102 {
1103 pClient->State.enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
1104
1105 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1106 }
1107 else
1108 rc = VERR_NOT_IMPLEMENTED;
1109
1110 return rc;
1111}
1112
1113static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1114{
1115 RT_NOREF(cArgs, apcszArgs);
1116
1117 char szOSInfo[64];
1118 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
1119 if (RT_SUCCESS(rc))
1120 rc = rtFtpServerSendReplyStr(pClient, szOSInfo);
1121
1122 return rc;
1123}
1124
1125static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1126{
1127 if (cArgs != 1)
1128 return VERR_INVALID_PARAMETER;
1129
1130 const char *pcszType = apcszArgs[0];
1131
1132 int rc = VINF_SUCCESS;
1133
1134 if (!RTStrICmp(pcszType, "A"))
1135 {
1136 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
1137 }
1138 else if (!RTStrICmp(pcszType, "I")) /* Image (binary). */
1139 {
1140 pClient->State.enmDataType = RTFTPSERVER_DATA_TYPE_IMAGE;
1141 }
1142 else /** @todo Support "E" (EBCDIC) and/or "L <size>" (custom)? */
1143 rc = VERR_NOT_IMPLEMENTED;
1144
1145 if (RT_SUCCESS(rc))
1146 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_OKAY);
1147
1148 return rc;
1149}
1150
1151static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
1152{
1153 if (cArgs != 1)
1154 return VERR_INVALID_PARAMETER;
1155
1156 const char *pcszUser = apcszArgs[0];
1157 AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
1158
1159 rtFtpServerClientStateReset(&pClient->State);
1160
1161 int rc = rtFtpServerLookupUser(pClient, pcszUser);
1162 if (RT_SUCCESS(rc))
1163 {
1164 pClient->State.pszUser = RTStrDup(pcszUser);
1165 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
1166
1167 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
1168 }
1169 else
1170 {
1171 pClient->State.cFailedLoginAttempts++;
1172
1173 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
1174 if (RT_SUCCESS(rc))
1175 rc = rc2;
1176 }
1177
1178 return rc;
1179}
1180
1181
1182/*********************************************************************************************************************************
1183* Internal server functions *
1184*********************************************************************************************************************************/
1185
1186/**
1187 * Parses FTP command arguments handed in by the client.
1188 *
1189 * @returns VBox status code.
1190 * @param pcszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
1191 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
1192 * @param ppapcszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
1193 */
1194static int rtFtpServerCmdArgsParse(const char *pcszCmdParms, uint8_t *pcArgs, char ***ppapcszArgs)
1195{
1196 *pcArgs = 0;
1197 *ppapcszArgs = NULL;
1198
1199 if (!pcszCmdParms) /* No parms given? Bail out early. */
1200 return VINF_SUCCESS;
1201
1202 /** @todo Anything else to do here? */
1203 /** @todo Check if quoting is correct. */
1204
1205 int cArgs = 0;
1206 int rc = RTGetOptArgvFromString(ppapcszArgs, &cArgs, pcszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
1207 if (RT_SUCCESS(rc))
1208 {
1209 if (cArgs <= UINT8_MAX)
1210 {
1211 *pcArgs = (uint8_t)cArgs;
1212 }
1213 else
1214 rc = VERR_INVALID_PARAMETER;
1215 }
1216
1217 return rc;
1218}
1219
1220/**
1221 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
1222 *
1223 * @param ppapcszArgs Argument string array to free.
1224 */
1225static void rtFtpServerCmdArgsFree(char **ppapcszArgs)
1226{
1227 RTGetOptArgvFree(ppapcszArgs);
1228}
1229
1230/**
1231 * Main function for processing client commands for the control connection.
1232 *
1233 * @returns VBox status code.
1234 * @param pClient Client to process commands for.
1235 * @param pcszCmd Command string to parse and handle.
1236 * @param cbCmd Size (in bytes) of command string.
1237 */
1238static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient, char *pcszCmd, size_t cbCmd)
1239{
1240 /* Make sure to terminate the string in any case. */
1241 pcszCmd[RT_MIN(RTFTPSERVER_MAX_CMD_LEN, cbCmd)] = '\0';
1242
1243 /* A tiny bit of sanitation. */
1244 RTStrStripL(pcszCmd);
1245
1246 /* First, terminate string by finding the command end marker (telnet style). */
1247 /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
1248 char *pszCmdEnd = RTStrIStr(pcszCmd, "\r\n");
1249 if (pszCmdEnd)
1250 *pszCmdEnd = '\0';
1251
1252 int rcCmd = VINF_SUCCESS;
1253
1254 uint8_t cArgs = 0;
1255 char **papszArgs = NULL;
1256 int rc = rtFtpServerCmdArgsParse(pcszCmd, &cArgs, &papszArgs);
1257 if ( RT_SUCCESS(rc)
1258 && cArgs) /* At least the actual command (without args) must be present. */
1259 {
1260 LogFlowFunc(("Handling command '%s'\n", papszArgs[0]));
1261
1262 unsigned i = 0;
1263 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
1264 {
1265 if (!RTStrICmp(papszArgs[0], g_aCmdMap[i].szCmd))
1266 {
1267 /* Save timestamp of last command sent. */
1268 pClient->State.tsLastCmdMs = RTTimeMilliTS();
1269
1270 rcCmd = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
1271 break;
1272 }
1273 }
1274
1275 rtFtpServerCmdArgsFree(papszArgs);
1276
1277 if (i == RT_ELEMENTS(g_aCmdMap))
1278 {
1279 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
1280 if (RT_SUCCESS(rc))
1281 rc = rc2;
1282
1283 LogFlowFunc(("Command not implemented\n", papszArgs[0]));
1284 return rc;
1285 }
1286
1287 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
1288 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
1289 if (fDisconnect)
1290 {
1291 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CLOSING_CTRL_CONN);
1292 if (RT_SUCCESS(rc))
1293 rc = rc2;
1294
1295 RTFTPSERVER_HANDLE_CALLBACK_VA(pfnOnUserDisconnect, pClient->State.pszUser);
1296 return rc;
1297 }
1298
1299 switch (rcCmd)
1300 {
1301 case VERR_INVALID_PARAMETER:
1302 RT_FALL_THROUGH();
1303 case VERR_INVALID_POINTER:
1304 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1305 break;
1306
1307 case VERR_NOT_IMPLEMENTED:
1308 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
1309 break;
1310
1311 default:
1312 break;
1313 }
1314 }
1315 else
1316 {
1317 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
1318 if (RT_SUCCESS(rc))
1319 rc = rc2;
1320 }
1321
1322 LogFlowFuncLeaveRC(rc);
1323 return rc;
1324}
1325
1326/**
1327 * Main loop for processing client commands.
1328 *
1329 * @returns VBox status code.
1330 * @param pClient Client to process commands for.
1331 */
1332static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
1333{
1334 int rc;
1335
1336 size_t cbRead;
1337 char szCmd[RTFTPSERVER_MAX_CMD_LEN + 1];
1338
1339 for (;;)
1340 {
1341 rc = RTTcpSelectOne(pClient->hSocket, 200 /* ms */); /** @todo Can we improve here? Using some poll events or so? */
1342 if (RT_SUCCESS(rc))
1343 {
1344 rc = RTTcpReadNB(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
1345 if ( RT_SUCCESS(rc)
1346 && cbRead)
1347 {
1348 AssertBreakStmt(cbRead <= sizeof(szCmd), rc = VERR_BUFFER_OVERFLOW);
1349 rc = rtFtpServerProcessCommands(pClient, szCmd, cbRead);
1350 }
1351 }
1352 else
1353 {
1354 if (rc == VERR_TIMEOUT)
1355 rc = VINF_SUCCESS;
1356
1357 if (RT_FAILURE(rc))
1358 break;
1359
1360 PRTFTPSERVERDATACONN pDataConn = &pClient->DataConn;
1361
1362 if ( ASMAtomicReadBool(&pDataConn->fStarted)
1363 && ASMAtomicReadBool(&pDataConn->fStopped))
1364 {
1365 Assert(pDataConn->rc != VERR_IPE_UNINITIALIZED_STATUS);
1366
1367 rc = rtFtpServerSendReplyRc(pClient, RT_SUCCESS(pDataConn->rc)
1368 ? RTFTPSERVER_REPLY_CLOSING_DATA_CONN
1369 : RTFTPSERVER_REPLY_CONN_CLOSED_TRANSFER_ABORTED);
1370
1371 int rc2 = rtFtpServerDataConnDestroy(pClient, pDataConn);
1372 if (RT_SUCCESS(rc))
1373 rc = rc2;
1374 }
1375 }
1376 }
1377
1378 /* Make sure to close any open data connections. */
1379 int rc2 = rtFtpServerDataConnDestroy(pClient, &pClient->DataConn);
1380 if (RT_SUCCESS(rc))
1381 rc = rc2;
1382
1383 LogFlowFuncLeaveRC(rc);
1384 return rc;
1385}
1386
1387/**
1388 * Resets the client's state.
1389 *
1390 * @param pState Client state to reset.
1391 */
1392static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
1393{
1394 LogFlowFuncEnter();
1395
1396 RTStrFree(pState->pszUser);
1397 pState->pszUser = NULL;
1398
1399 pState->cFailedLoginAttempts = 0;
1400 pState->tsLastCmdMs = RTTimeMilliTS();
1401 pState->enmDataType = RTFTPSERVER_DATA_TYPE_ASCII;
1402 pState->enmStructType = RTFTPSERVER_STRUCT_TYPE_FILE;
1403}
1404
1405/**
1406 * Per-client thread for serving the server's control connection.
1407 *
1408 * @returns VBox status code.
1409 * @param hSocket Socket handle to use for the control connection.
1410 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
1411 */
1412static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
1413{
1414 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
1415 RTFTPSERVER_VALID_RETURN(pThis);
1416
1417 RTFTPSERVERCLIENT Client;
1418 RT_ZERO(Client);
1419
1420 Client.pServer = pThis;
1421 Client.hSocket = hSocket;
1422
1423 LogFlowFunc(("New client connected\n"));
1424
1425 rtFtpServerClientStateReset(&Client.State);
1426
1427 /*
1428 * Send welcome message.
1429 * Note: Some clients (like FileZilla / Firefox) expect a message together with the reply code,
1430 * so make sure to include at least *something*.
1431 */
1432 int rc = rtFtpServerSendReplyRcEx(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER,
1433 "Welcome!");
1434 if (RT_SUCCESS(rc))
1435 {
1436 ASMAtomicIncU32(&pThis->cClients);
1437
1438 rc = rtFtpServerProcessCommands(&Client);
1439
1440 ASMAtomicDecU32(&pThis->cClients);
1441 }
1442
1443 rtFtpServerClientStateReset(&Client.State);
1444
1445 return rc;
1446}
1447
1448RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
1449 PRTFTPSERVERCALLBACKS pCallbacks, void *pvUser, size_t cbUser)
1450{
1451 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
1452 AssertPtrReturn(pcszAddress, VERR_INVALID_POINTER);
1453 AssertReturn (uPort, VERR_INVALID_PARAMETER);
1454 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1455 /* pvUser is optional. */
1456
1457 int rc;
1458
1459 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
1460 if (pThis)
1461 {
1462 pThis->u32Magic = RTFTPSERVER_MAGIC;
1463 pThis->Callbacks = *pCallbacks;
1464 pThis->pvUser = pvUser;
1465 pThis->cbUser = cbUser;
1466
1467 rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
1468 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
1469 if (RT_SUCCESS(rc))
1470 {
1471 *phFTPServer = (RTFTPSERVER)pThis;
1472 }
1473 }
1474 else
1475 rc = VERR_NO_MEMORY;
1476
1477 return rc;
1478}
1479
1480RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
1481{
1482 if (hFTPServer == NIL_RTFTPSERVER)
1483 return VINF_SUCCESS;
1484
1485 PRTFTPSERVERINTERNAL pThis = hFTPServer;
1486 RTFTPSERVER_VALID_RETURN(pThis);
1487
1488 AssertPtr(pThis->pTCPServer);
1489
1490 int rc = RTTcpServerDestroy(pThis->pTCPServer);
1491 if (RT_SUCCESS(rc))
1492 {
1493 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
1494
1495 RTMemFree(pThis);
1496 }
1497
1498 return rc;
1499}
1500
Note: See TracBrowser for help on using the repository browser.

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