VirtualBox

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

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

IPRT/FTP: More work on data connection handling. bugref:9646

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