VirtualBox

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

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

IPRT/FTP: Made authentication workflow more flexible. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 24.7 KB
Line 
1/* $Id: ftp-server.cpp 82702 2020-01-09 16:18:28Z vboxsync $ */
2/** @file
3 * Generic FTP server (RFC 959) implementation.
4 */
5
6/*
7 * Copyright (C) 2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27/**
28 * Known limitations so far:
29 * - UTF-8 support only.
30 * - No support for writing / modifying ("DELE", "MKD", "RMD", "STOR", ++).
31 * - No FTPS / SFTP support.
32 * - No passive mode ("PASV") support.
33 * - No proxy support.
34 * - No FXP support.
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#define LOG_GROUP RTLOGGROUP_FTP
42#include <iprt/asm.h>
43#include <iprt/assert.h>
44#include <iprt/errcore.h>
45#include <iprt/ftp.h>
46#include <iprt/getopt.h>
47#include <iprt/mem.h>
48#include <iprt/log.h>
49#include <iprt/path.h>
50#include <iprt/poll.h>
51#include <iprt/socket.h>
52#include <iprt/string.h>
53#include <iprt/system.h>
54#include <iprt/tcp.h>
55
56#include "internal/magics.h"
57
58
59/*********************************************************************************************************************************
60* Structures and Typedefs *
61*********************************************************************************************************************************/
62/**
63 * Internal FTP server instance.
64 */
65typedef struct RTFTPSERVERINTERNAL
66{
67 /** Magic value. */
68 uint32_t u32Magic;
69 /** Callback table. */
70 RTFTPSERVERCALLBACKS Callbacks;
71 /** Pointer to TCP server instance. */
72 PRTTCPSERVER pTCPServer;
73 /** Number of currently connected clients. */
74 uint32_t cClients;
75} RTFTPSERVERINTERNAL;
76/** Pointer to an internal FTP server instance. */
77typedef RTFTPSERVERINTERNAL *PRTFTPSERVERINTERNAL;
78
79
80/*********************************************************************************************************************************
81* Defined Constants And Macros *
82*********************************************************************************************************************************/
83/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
84#define RTFTPSERVER_VALID_RETURN_RC(hFTPServer, a_rc) \
85 do { \
86 AssertPtrReturn((hFTPServer), (a_rc)); \
87 AssertReturn((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC, (a_rc)); \
88 } while (0)
89
90/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
91#define RTFTPSERVER_VALID_RETURN(hFTPServer) RTFTPSERVER_VALID_RETURN_RC((hFTPServer), VERR_INVALID_HANDLE)
92
93/** Validates a handle and returns (void) if not valid. */
94#define RTFTPSERVER_VALID_RETURN_VOID(hFTPServer) \
95 do { \
96 AssertPtrReturnVoid(hFTPServer); \
97 AssertReturnVoid((hFTPServer)->u32Magic == RTFTPSERVER_MAGIC); \
98 } while (0)
99
100/** Supported FTP server command IDs.
101 * Alphabetically, named after their official command names. */
102typedef enum RTFTPSERVER_CMD
103{
104 /** Invalid command, do not use. Always must come first. */
105 RTFTPSERVER_CMD_INVALID = 0,
106 /** Aborts the current command on the server. */
107 RTFTPSERVER_CMD_ABOR,
108 /** Changes the current working directory. */
109 RTFTPSERVER_CMD_CDUP,
110 /** Changes the current working directory. */
111 RTFTPSERVER_CMD_CWD,
112 /** Lists a directory. */
113 RTFTPSERVER_CMD_LIST,
114 /** Sets the transfer mode. */
115 RTFTPSERVER_CMD_MODE,
116 /** Sends a nop ("no operation") to the server. */
117 RTFTPSERVER_CMD_NOOP,
118 /** Sets the password for authentication. */
119 RTFTPSERVER_CMD_PASS,
120 /** Sets the port to use for the data connection. */
121 RTFTPSERVER_CMD_PORT,
122 /** Gets the current working directory. */
123 RTFTPSERVER_CMD_PWD,
124 /** Terminates the session (connection). */
125 RTFTPSERVER_CMD_QUIT,
126 /** Retrieves a specific file. */
127 RTFTPSERVER_CMD_RETR,
128 /** Recursively gets a directory (and its contents). */
129 RTFTPSERVER_CMD_RGET,
130 /** Retrieves the current status of a transfer. */
131 RTFTPSERVER_CMD_STAT,
132 /** Gets the server's OS info. */
133 RTFTPSERVER_CMD_SYST,
134 /** Sets the (data) representation type. */
135 RTFTPSERVER_CMD_TYPE,
136 /** Sets the user name for authentication. */
137 RTFTPSERVER_CMD_USER,
138 /** End marker. */
139 RTFTPSERVER_CMD_LAST,
140 /** The usual 32-bit hack. */
141 RTFTPSERVER_CMD_32BIT_HACK = 0x7fffffff
142} RTFTPSERVER_CMD;
143
144/**
145 * Structure for maintaining an internal FTP server client.
146 */
147typedef struct RTFTPSERVERCLIENT
148{
149 /** Pointer to internal server state. */
150 PRTFTPSERVERINTERNAL pServer;
151 /** Socket handle the client is bound to. */
152 RTSOCKET hSocket;
153 /** Actual client state. */
154 RTFTPSERVERCLIENTSTATE State;
155} RTFTPSERVERCLIENT;
156/** Pointer to an internal FTP server client state. */
157typedef RTFTPSERVERCLIENT *PRTFTPSERVERCLIENT;
158
159/** Function pointer declaration for a specific FTP server command handler. */
160typedef DECLCALLBACK(int) FNRTFTPSERVERCMD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs);
161/** Pointer to a FNRTFTPSERVERCMD(). */
162typedef FNRTFTPSERVERCMD *PFNRTFTPSERVERCMD;
163
164/** Handles a FTP server callback with no arguments and returns. */
165#define RTFTPSERVER_HANDLE_CALLBACK_RET(a_Name) \
166 do \
167 { \
168 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
169 if (pCallbacks->a_Name) \
170 { \
171 RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
172 return pCallbacks->a_Name(&Data); \
173 } \
174 } while (0)
175
176/** Handles a FTP server callback with arguments and returns. */
177#define RTFTPSERVER_HANDLE_CALLBACK_VA_RET(a_Name, ...) \
178 do \
179 { \
180 PRTFTPSERVERCALLBACKS pCallbacks = &pClient->pServer->Callbacks; \
181 if (pCallbacks->a_Name) \
182 { \
183 RTFTPCALLBACKDATA Data = { &pClient->State, pCallbacks->pvUser, pCallbacks->cbUser }; \
184 return pCallbacks->a_Name(&Data, __VA_ARGS__); \
185 } \
186 } while (0)
187
188/**
189 * Function prototypes for command handlers.
190 */
191static FNRTFTPSERVERCMD rtFtpServerHandleABOR;
192static FNRTFTPSERVERCMD rtFtpServerHandleCDUP;
193static FNRTFTPSERVERCMD rtFtpServerHandleCWD;
194static FNRTFTPSERVERCMD rtFtpServerHandleLIST;
195static FNRTFTPSERVERCMD rtFtpServerHandleMODE;
196static FNRTFTPSERVERCMD rtFtpServerHandleNOOP;
197static FNRTFTPSERVERCMD rtFtpServerHandlePASS;
198static FNRTFTPSERVERCMD rtFtpServerHandlePORT;
199static FNRTFTPSERVERCMD rtFtpServerHandlePWD;
200static FNRTFTPSERVERCMD rtFtpServerHandleQUIT;
201static FNRTFTPSERVERCMD rtFtpServerHandleRETR;
202static FNRTFTPSERVERCMD rtFtpServerHandleRGET;
203static FNRTFTPSERVERCMD rtFtpServerHandleSTAT;
204static FNRTFTPSERVERCMD rtFtpServerHandleSYST;
205static FNRTFTPSERVERCMD rtFtpServerHandleTYPE;
206static FNRTFTPSERVERCMD rtFtpServerHandleUSER;
207
208/**
209 * Structure for maintaining a single command entry for the command table.
210 */
211typedef struct RTFTPSERVER_CMD_ENTRY
212{
213 /** Command ID. */
214 RTFTPSERVER_CMD enmCmd;
215 /** Command represented as ASCII string. */
216 char szCmd[RTFTPSERVER_MAX_CMD_LEN];
217 /** Function pointer invoked to handle the command. */
218 PFNRTFTPSERVERCMD pfnCmd;
219} RTFTPSERVER_CMD_ENTRY;
220
221/**
222 * Table of handled commands.
223 */
224const RTFTPSERVER_CMD_ENTRY g_aCmdMap[] =
225{
226 { RTFTPSERVER_CMD_ABOR, "ABOR", rtFtpServerHandleABOR },
227 { RTFTPSERVER_CMD_CDUP, "CDUP", rtFtpServerHandleCDUP },
228 { RTFTPSERVER_CMD_CWD, "CWD", rtFtpServerHandleCWD },
229 { RTFTPSERVER_CMD_LIST, "LIST", rtFtpServerHandleLIST },
230 { RTFTPSERVER_CMD_MODE, "MODE", rtFtpServerHandleMODE },
231 { RTFTPSERVER_CMD_NOOP, "NOOP", rtFtpServerHandleNOOP },
232 { RTFTPSERVER_CMD_PASS, "PASS", rtFtpServerHandlePASS },
233 { RTFTPSERVER_CMD_PORT, "PORT", rtFtpServerHandlePORT },
234 { RTFTPSERVER_CMD_PWD, "PWD", rtFtpServerHandlePWD },
235 { RTFTPSERVER_CMD_QUIT, "QUIT", rtFtpServerHandleQUIT },
236 { RTFTPSERVER_CMD_RETR, "RETR", rtFtpServerHandleRETR },
237 { RTFTPSERVER_CMD_RGET, "RGET", rtFtpServerHandleRGET },
238 { RTFTPSERVER_CMD_STAT, "STAT", rtFtpServerHandleSTAT },
239 { RTFTPSERVER_CMD_SYST, "SYST", rtFtpServerHandleSYST },
240 { RTFTPSERVER_CMD_TYPE, "TYPE", rtFtpServerHandleTYPE },
241 { RTFTPSERVER_CMD_USER, "USER", rtFtpServerHandleUSER },
242 { RTFTPSERVER_CMD_LAST, "", NULL }
243};
244
245
246/*********************************************************************************************************************************
247* Protocol Functions *
248*********************************************************************************************************************************/
249
250/**
251 * Replies a (three digit) reply code back to the client.
252 *
253 * @returns VBox status code.
254 * @param pClient Client to reply to.
255 * @param enmReply Reply code to send.
256 */
257static int rtFtpServerSendReplyRc(PRTFTPSERVERCLIENT pClient, RTFTPSERVER_REPLY enmReply)
258{
259 char szReply[32];
260 RTStrPrintf2(szReply, sizeof(szReply), "%RU32\r\n", enmReply);
261
262 return RTTcpWrite(pClient->hSocket, szReply, strlen(szReply) + 1);
263}
264
265/**
266 * Replies a string back to the client.
267 *
268 * @returns VBox status code.
269 * @param pClient Client to reply to.
270 * @param pcszStr String to reply.
271 */
272static int rtFtpServerSendReplyStr(PRTFTPSERVERCLIENT pClient, const char *pcszStr)
273{
274 char *pszReply;
275 int rc = RTStrAPrintf(&pszReply, "%s\r\n", pcszStr);
276 if (RT_SUCCESS(rc))
277 {
278 rc = RTTcpWrite(pClient->hSocket, pszReply, strlen(pszReply) + 1);
279 RTStrFree(pszReply);
280 return rc;
281 }
282
283 return VERR_NO_MEMORY;
284}
285
286/**
287 * Looks up an user account.
288 *
289 * @returns VBox status code, or VERR_NOT_FOUND if user has not been found.
290 * @param pClient Client to look up user for.
291 * @param pcszUser User name to look up.
292 */
293static int rtFtpServerLookupUser(PRTFTPSERVERCLIENT pClient, const char *pcszUser)
294{
295 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserConnect, pcszUser);
296
297 return VERR_NOT_FOUND;
298}
299
300/**
301 * Handles the actual client authentication.
302 *
303 * @returns VBox status code, or VERR_ACCESS_DENIED if authentication failed.
304 * @param pClient Client to authenticate.
305 * @param pcszUser User name to authenticate with.
306 * @param pcszPassword Password to authenticate with.
307 */
308static int rtFtpServerAuthenticate(PRTFTPSERVERCLIENT pClient, const char *pcszUser, const char *pcszPassword)
309{
310 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnUserAuthenticate, pcszUser, pcszPassword);
311
312 return VERR_ACCESS_DENIED;
313}
314
315
316/*********************************************************************************************************************************
317* Command Protocol Handlers *
318*********************************************************************************************************************************/
319
320static int rtFtpServerHandleABOR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
321{
322 RT_NOREF(pClient, cArgs, apcszArgs);
323
324 /** @todo Anything to do here? */
325 return VINF_SUCCESS;
326}
327
328static int rtFtpServerHandleCDUP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
329{
330 RT_NOREF(pClient, cArgs, apcszArgs);
331
332 /** @todo Anything to do here? */
333 return VINF_SUCCESS;
334}
335
336static int rtFtpServerHandleCWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
337{
338 AssertPtrReturn(apcszArgs, VERR_INVALID_POINTER);
339
340 if (cArgs != 1)
341 return VERR_INVALID_PARAMETER;
342
343 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnPathSetCurrent, apcszArgs[0]);
344
345 return VERR_NOT_IMPLEMENTED;
346}
347
348static int rtFtpServerHandleLIST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
349{
350 RT_NOREF(cArgs, apcszArgs);
351
352 void *pvData = NULL;
353 size_t cbData = 0;
354
355 RTFTPSERVER_HANDLE_CALLBACK_VA_RET(pfnOnList, &pvData, &cbData);
356
357 return VERR_NOT_IMPLEMENTED;
358}
359
360static int rtFtpServerHandleMODE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
361{
362 RT_NOREF(pClient, cArgs, apcszArgs);
363
364 /** @todo Anything to do here? */
365 return VINF_SUCCESS;
366}
367
368static int rtFtpServerHandleNOOP(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
369{
370 RT_NOREF(pClient, cArgs, apcszArgs);
371
372 /* Nothing to do here. */
373 return VINF_SUCCESS;
374}
375
376static int rtFtpServerHandlePASS(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
377{
378 if (cArgs != 1)
379 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
380
381 const char *pcszPassword = apcszArgs[0];
382 AssertPtrReturn(pcszPassword, VERR_INVALID_PARAMETER);
383
384 int rc = rtFtpServerAuthenticate(pClient, pClient->State.pszUser, pcszPassword);
385 if (RT_SUCCESS(rc))
386 {
387 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_LOGGED_IN_PROCEED);
388 }
389 else
390 {
391 pClient->State.cFailedLoginAttempts++;
392
393 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
394 if (RT_SUCCESS(rc))
395 rc = rc2;
396 }
397
398 return rc;
399}
400
401static int rtFtpServerHandlePORT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
402{
403 RT_NOREF(pClient, cArgs, apcszArgs);
404
405 /** @todo Anything to do here? */
406 return VINF_SUCCESS;
407}
408
409static int rtFtpServerHandlePWD(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
410{
411 RT_NOREF(cArgs, apcszArgs);
412
413#if 0
414 char *pszReply;
415 int rc = RTStrAPrintf(&pszReply, "%s\r\n", pClient->szCWD);
416 if (RT_SUCCESS(rc))
417 {
418 rc = RTTcpWrite(pClient->hSocket, pszReply, strlen(pszReply) + 1);
419 RTStrFree(pszReply);
420 return rc;
421 }
422
423 return VERR_NO_MEMORY;
424#endif
425
426 RT_NOREF(pClient);
427 return 0;
428}
429
430static int rtFtpServerHandleQUIT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
431{
432 RT_NOREF(pClient, cArgs, apcszArgs);
433
434 /** @todo Anything to do here? */
435 return VINF_SUCCESS;
436}
437
438static int rtFtpServerHandleRETR(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
439{
440 RT_NOREF(pClient, cArgs, apcszArgs);
441
442 /** @todo Anything to do here? */
443 return VINF_SUCCESS;
444}
445
446static int rtFtpServerHandleRGET(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
447{
448 RT_NOREF(pClient, cArgs, apcszArgs);
449
450 /** @todo Anything to do here? */
451 return VINF_SUCCESS;
452}
453
454static int rtFtpServerHandleSTAT(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
455{
456 RT_NOREF(pClient, cArgs, apcszArgs);
457
458 /** @todo Anything to do here? */
459 return VINF_SUCCESS;
460}
461
462static int rtFtpServerHandleSYST(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
463{
464 RT_NOREF(cArgs, apcszArgs);
465
466 char szOSInfo[64];
467 int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szOSInfo, sizeof(szOSInfo));
468 if (RT_SUCCESS(rc))
469 rc = rtFtpServerSendReplyStr(pClient, szOSInfo);
470
471 return rc;
472}
473
474static int rtFtpServerHandleTYPE(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
475{
476 RT_NOREF(pClient, cArgs, apcszArgs);
477
478 /** @todo Anything to do here? */
479 return VINF_SUCCESS;
480}
481
482static int rtFtpServerHandleUSER(PRTFTPSERVERCLIENT pClient, uint8_t cArgs, const char * const *apcszArgs)
483{
484 if (cArgs != 1)
485 return rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
486
487 const char *pcszUser = apcszArgs[0];
488 AssertPtrReturn(pcszUser, VERR_INVALID_PARAMETER);
489
490 if (pClient->State.pszUser)
491 {
492 RTStrFree(pClient->State.pszUser);
493 pClient->State.pszUser = NULL;
494 }
495
496 int rc = rtFtpServerLookupUser(pClient, pcszUser);
497 if (RT_SUCCESS(rc))
498 {
499 pClient->State.pszUser = RTStrDup(pcszUser);
500 AssertPtrReturn(pClient->State.pszUser, VERR_NO_MEMORY);
501
502 rc = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_USERNAME_OKAY_NEED_PASSWORD);
503 }
504 else
505 {
506 pClient->State.cFailedLoginAttempts++;
507
508 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_NOT_LOGGED_IN);
509 if (RT_SUCCESS(rc))
510 rc = rc2;
511 }
512
513 return rc;
514}
515
516
517/*********************************************************************************************************************************
518* Internal server functions *
519*********************************************************************************************************************************/
520
521/**
522 * Parses FTP command arguments handed in by the client.
523 *
524 * @returns VBox status code.
525 * @param pcszCmdParms Pointer to command arguments, if any. Can be NULL if no arguments are given.
526 * @param pcArgs Returns the number of parsed arguments, separated by a space (hex 0x20).
527 * @param ppapcszArgs Returns the string array of parsed arguments. Needs to be free'd with rtFtpServerCmdArgsFree().
528 */
529static int rtFtpServerCmdArgsParse(const char *pcszCmdParms, uint8_t *pcArgs, char ***ppapcszArgs)
530{
531 *pcArgs = 0;
532 *ppapcszArgs = NULL;
533
534 if (!pcszCmdParms) /* No parms given? Bail out early. */
535 return VINF_SUCCESS;
536
537 /** @todo Anything else to do here? */
538 /** @todo Check if quoting is correct. */
539
540 int cArgs = 0;
541 int rc = RTGetOptArgvFromString(ppapcszArgs, &cArgs, pcszCmdParms, RTGETOPTARGV_CNV_QUOTE_MS_CRT, " " /* Separators */);
542 if (RT_SUCCESS(rc))
543 {
544 if (cArgs <= UINT8_MAX)
545 {
546 *pcArgs = (uint8_t)cArgs;
547 }
548 else
549 rc = VERR_INVALID_PARAMETER;
550 }
551
552 return rc;
553}
554
555/**
556 * Frees a formerly argument string array parsed by rtFtpServerCmdArgsParse().
557 *
558 * @param ppapcszArgs Argument string array to free.
559 */
560static void rtFtpServerCmdArgsFree(char **ppapcszArgs)
561{
562 RTGetOptArgvFree(ppapcszArgs);
563}
564
565/**
566 * Main loop for processing client commands.
567 *
568 * @returns VBox status code.
569 * @param pClient Client to process commands for.
570 */
571static int rtFtpServerProcessCommands(PRTFTPSERVERCLIENT pClient)
572{
573 int rc;
574
575 for (;;)
576 {
577 size_t cbRead;
578 char szCmd[RTFTPSERVER_MAX_CMD_LEN];
579 rc = RTTcpRead(pClient->hSocket, szCmd, sizeof(szCmd), &cbRead);
580 if (RT_SUCCESS(rc))
581 {
582 /* Make sure to terminate the string in any case. */
583 szCmd[RTFTPSERVER_MAX_CMD_LEN - 1] = '\0';
584
585 /* A tiny bit of sanitation. */
586 RTStrStripL(szCmd);
587
588 /* First, terminate string by finding the command end marker (telnet style). */
589 /** @todo Not sure if this is entirely correct and/or needs tweaking; good enough for now as it seems. */
590 char *pszCmdEnd = RTStrIStr(szCmd, "\r\n");
591 if (pszCmdEnd)
592 *pszCmdEnd = '\0';
593
594 uint8_t cArgs = 0;
595 char **papszArgs = NULL;
596 rc = rtFtpServerCmdArgsParse(szCmd, &cArgs, &papszArgs);
597 if ( RT_SUCCESS(rc)
598 && cArgs) /* At least the actual command (without args) must be present. */
599 {
600 unsigned i = 0;
601 for (; i < RT_ELEMENTS(g_aCmdMap); i++)
602 {
603 if (!RTStrICmp(papszArgs[0], g_aCmdMap[i].szCmd))
604 {
605 /* Save timestamp of last command sent. */
606 pClient->State.tsLastCmdMs = RTTimeMilliTS();
607
608 rc = g_aCmdMap[i].pfnCmd(pClient, cArgs - 1, cArgs > 1 ? &papszArgs[1] : NULL);
609 break;
610 }
611 }
612
613 rtFtpServerCmdArgsFree(papszArgs);
614
615 if (i == RT_ELEMENTS(g_aCmdMap))
616 {
617 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_IMPL);
618 if (RT_SUCCESS(rc))
619 rc = rc2;
620 }
621
622 const bool fDisconnect = g_aCmdMap[i].enmCmd == RTFTPSERVER_CMD_QUIT
623 || pClient->State.cFailedLoginAttempts >= 3; /** @todo Make this dynamic. */
624 if (fDisconnect)
625 {
626 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_CLOSING_CTRL_CONN);
627 if (RT_SUCCESS(rc))
628 rc = rc2;
629
630 RTFTPSERVER_HANDLE_CALLBACK_RET(pfnOnUserDisconnect);
631 break;
632 }
633 }
634 else
635 {
636 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_INVALID_PARAMETERS);
637 if (RT_SUCCESS(rc))
638 rc = rc2;
639 }
640 }
641 else
642 {
643 int rc2 = rtFtpServerSendReplyRc(pClient, RTFTPSERVER_REPLY_ERROR_CMD_NOT_RECOGNIZED);
644 if (RT_SUCCESS(rc))
645 rc = rc2;
646 }
647 }
648
649 return rc;
650}
651
652/**
653 * Resets the client's state.
654 *
655 * @param pState Client state to reset.
656 */
657static void rtFtpServerClientStateReset(PRTFTPSERVERCLIENTSTATE pState)
658{
659 RTStrFree(pState->pszUser);
660 pState->pszUser = NULL;
661
662 pState->tsLastCmdMs = RTTimeMilliTS();
663}
664
665/**
666 * Per-client thread for serving the server's control connection.
667 *
668 * @returns VBox status code.
669 * @param hSocket Socket handle to use for the control connection.
670 * @param pvUser User-provided arguments. Of type PRTFTPSERVERINTERNAL.
671 */
672static DECLCALLBACK(int) rtFtpServerClientThread(RTSOCKET hSocket, void *pvUser)
673{
674 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)pvUser;
675 RTFTPSERVER_VALID_RETURN(pThis);
676
677 RTFTPSERVERCLIENT Client;
678 RT_ZERO(Client);
679
680 Client.pServer = pThis;
681 Client.hSocket = hSocket;
682
683 rtFtpServerClientStateReset(&Client.State);
684
685 /* Send welcome message. */
686 int rc = rtFtpServerSendReplyRc(&Client, RTFTPSERVER_REPLY_READY_FOR_NEW_USER);
687 if (RT_SUCCESS(rc))
688 {
689 ASMAtomicIncU32(&pThis->cClients);
690
691 rc = rtFtpServerProcessCommands(&Client);
692
693 ASMAtomicDecU32(&pThis->cClients);
694 }
695
696 rtFtpServerClientStateReset(&Client.State);
697
698 return rc;
699}
700
701RTR3DECL(int) RTFtpServerCreate(PRTFTPSERVER phFTPServer, const char *pcszAddress, uint16_t uPort,
702 PRTFTPSERVERCALLBACKS pCallbacks)
703{
704 AssertPtrReturn(phFTPServer, VERR_INVALID_POINTER);
705 AssertPtrReturn(pcszAddress, VERR_INVALID_POINTER);
706 AssertReturn (uPort, VERR_INVALID_PARAMETER);
707 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
708
709 int rc;
710
711 PRTFTPSERVERINTERNAL pThis = (PRTFTPSERVERINTERNAL)RTMemAllocZ(sizeof(RTFTPSERVERINTERNAL));
712 if (pThis)
713 {
714 pThis->u32Magic = RTFTPSERVER_MAGIC;
715 pThis->Callbacks = *pCallbacks;
716
717 rc = RTTcpServerCreate(pcszAddress, uPort, RTTHREADTYPE_DEFAULT, "ftpsrv",
718 rtFtpServerClientThread, pThis /* pvUser */, &pThis->pTCPServer);
719 }
720 else
721 rc = VERR_NO_MEMORY;
722
723 return rc;
724}
725
726RTR3DECL(int) RTFtpServerDestroy(RTFTPSERVER hFTPServer)
727{
728 if (hFTPServer == NIL_RTFTPSERVER)
729 return VINF_SUCCESS;
730
731 PRTFTPSERVERINTERNAL pThis = hFTPServer;
732 RTFTPSERVER_VALID_RETURN(pThis);
733
734 AssertPtr(pThis->pTCPServer);
735
736 int rc = RTTcpServerDestroy(pThis->pTCPServer);
737 if (RT_SUCCESS(rc))
738 {
739 pThis->u32Magic = RTFTPSERVER_MAGIC_DEAD;
740
741 RTMemFree(pThis);
742 }
743
744 return rc;
745}
746
Note: See TracBrowser for help on using the repository browser.

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