![]() |
General Programming »
Internet / Network »
FTP
Intermediate
License: The MIT License
CFtpServer: A complete, fast, and reliable FTP server classBy Poumailloux JulienAn article on using the CFtpServer C++ class. |
VC6, Windows, MFC, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
FTP servers are great; they allow you to store data you want to save, remotely. However, most of the time, you have to install a lot of things on the remote computer: the FTP server itself, including, most of the time, dozens of files, their dependencies, etc.
Here is where the CFtpServer class is useful:
CFtpServer supports 38 commands: USER, PASS, SITE, SITE EXEC, HELP, REIN, SYST, STRU, MODE, TYPE, PORT, PASV, LIST, NLST, CWD, XCWD, FEAT, MDTM, PWD, XPWD, CDUP, XCUP, STAT, ABOR, REST, RETR, STOR, APPE, STOU, SIZE, DELE, RNFR, RNTO, MKD, XMKD, RMD, XRMD.
CFtpServer is multithreaded, and runs in parallel with your code, preventing blocking operations from impeding your application's performance.
First of all, on Win32, we need to initialize Winsock:
#ifdef WIN32
WSADATA WSAData;
WORD wVersionRequested = MAKEWORD( 1, 1 );
if( WSAStartup(wVersionRequested, &WSAData) != 0 ) {
return 0;
}
#endif
The class name is CFtpServer. First, we need to declare the class:
CFtpServer FtpServer;
Then, we configure it:
FtpServer.SetDataPortRange( 100,900 ); FtpServer.AllowAnonymous( true, "C:/anonymous" );
We tell the class which TCP-port can be used for transfers (here, [100-999]), and we tell the class to allow anonymous users.
Then, we create a user:
CFtpServer::UserNode *FtpUser =
FtpServer.AddUser( "test", "pass", "C:\\");
Here, the user's login is test, his password is pass, and his start directory is: C:/. Besides this, we can check if the user was successfully created, by checking the pointer returned by FtpServer.AddUser. If it is equal to NULL, the user hasn't been created, and the start path may not exist.
After that, we set the maximum number of clients who can log in as this user, and we give him some privileges:
FtpServer.SetUserMaxClient( FtpUser, CFtpServer::Unlimited );
FtpServer.SetUsetPriv( FtpUser, CFtpServer::READFILE |
CFtpServer::WRITEFILE | CFtpServer::LIST |
CFtpServer::DELETEFILE | CFtpServer::CREATEDIR |
CFtpServer::DELETEDIR );
FtpUser will be given the right to list files and directories, to read, write (including creating files), and delete (including renaming files) files, and to create and delete directories.
At last, we ask the class to start listening - here, it will listen on the TCP-port 21 and on all network interfaces - and to accept incoming clients:
// If you only want to listen on the TCP Loopback interface, // replace 'INNADDR_ANY' by 'inet_addr("127.0.0.1")'. FtpServer.StartListening( INADDR_ANY, 21 ); FtpServer.StartAccepting();
If your program has nothing else to do, we ask the main thread to wait, forever:
while( 1 ) #ifdef WIN32 Sleep( 1000 ); #else usleep( 1000 ); #endif
Here we go, the FTP server is running!
Now, the public part of the CFtpServer class:
public: /* Constructor */ CFtpServer(void); /* Destructor */ ~CFtpServer(void); /**************************************** * USER ****************************************/ /* The Structure which will be allocated for each User. */ struct UserNode { int iMaxClient; unsigned char ucPriv; #ifdef CFTPSERVER_USE_EXTRACMD unsigned char ucExtraCmd; #endif char *szName; char *szPasswd; bool bIsEnabled; char *szStartDir; int iNbClient; struct UserNode *PrevUser; struct UserNode *NextUser; }; /* Enumerate the different Privilegies a User can get. */ enum { READFILE = 0x1, WRITEFILE = 0x2, DELETEFILE = 0x4, LIST = 0x8, CREATEDIR = 0x10, DELETEDIR = 0x20, }; /* Create a new User. Don't create a user named "anonymous", call AllowAnonymous();. Arguments: -the User Name. -the User Password. -the User Start directory. Returns: -on success: the adress of the New-User's CFtpServer::UserNode structure. -on error: NULL. */ struct CFtpServer::UserNode *AddUser( const char *szName, const char *szPasswd, const char* szStartDir ); /* Set the Privilegies of a User. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. -the user's privilegies of the CFtpServer Enum of privilegies, separated by the | operator. Returns: -on success: true. -on error: false. */ bool SetUserPriv( struct CFtpServer::UserNode *User, unsigned char ucPriv ); /* Delete a User, and by the way all the Clients connected to this User. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on success: true. -on error: false. */ bool DelUser( struct CFtpServer::UserNode * User ); /* Get the Head of the User List. Arguments: Returns: -the Head of the User List. */ struct CFtpServer::UserNode *GetUserListHead( void ) { return this->UserListHead; } /* Get the Last User of the List. Arguments: Returns: -the Last User of the List. */ struct CFtpServer::UserNode *GetUserListLast( void ) { return this->UserListLast; } /* Get the Next User of the List. Arguments: -a pointer to a CFtpServer::UserNode structure. Returns: -the Next User of the list. */ struct CFtpServer::UserNode *GetNextUser( const struct CFtpServer::UserNode *User ) { if( User ) return User->NextUser; return NULL; } /* Get the Previous User of the List. Arguments: -a pointer to a CFtpServer::UserNode structure. Returns: -the Previous User of the List. */ struct CFtpServer::UserNode *GetPreviousUser( const struct CFtpServer::UserNode *User ) { if( User ) return User->PrevUser; return NULL; } enum { Error = -2, None = -1, Unlimited = 0 }; /* Set the maximum number of Clients which can be connected to the User at a time. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. -the number of clients who will be able to be connected to the user at a time. CFtpServer::UserMaxClient.Unlimited: Unlimited. CFtpServer::UserMaxClient.None: None. Returns: -on success: true. -on error: false. */ bool SetUserMaxClient( struct CFtpServer::UserNode *User, int iMaxClient ); /* Get the maximum number of Clients which can be connected to the User at a time. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on success: the number of clients which can be connected to the User at a time. -on error: -2. */ unsigned int GetUserMaxClient( const struct CFtpServer::UserNode *User ) { if( User ) return User->iMaxClient; return 0; } /* Delete all the Users, and by the way all the Server's Clients connected to a User. */ void DelAllUser( void ); /* Get a User's privilegies Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on success: the user's privilegies concatenated with the bitwise inclusive binary operator "|". -on error: 0. */ unsigned char GetUserPriv( const struct CFtpServer::UserNode *User ) { if( User ) { return User->ucPriv; } else return 0; } /* Get a pointer to a User's Name. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on success: a pointer to the User's Name. -on error: NULL. */ const char* GetUserLogin( const struct CFtpServer::UserNode *User ) { if( User ) { return User->szName; } else return NULL; } /* Get a pointer to a User's Password. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on success: a pointer to the User's Password. -on error: NULL. */ const char* GetUserPasswd( const struct CFtpServer::UserNode *User ) { if( User ) { return User->szPasswd; } else return NULL; } /* Get a pointer to a User's Start Directory. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on success: a pointer to the User's Start Directory. -on error: NULL. */ const char* GetUserStartDir( const struct CFtpServer::UserNode *User ) { if( User ) { return User->szStartDir; } else return NULL; } #ifdef CFTPSERVER_USE_EXTRACMD /* Enum the Extra Commands a User can got. */ enum eFtpExtraCmd { ExtraCmd_EXEC = 0x1, }; /* Set the supported Extra-Commands of a User. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. -the user's Extra-Commands concatenated with the bitwise inclusive binary operator "|". Returns: -on success: true. -on error: false. */ bool SetUserExtraCmd( struct CFtpServer::UserNode *User, unsigned char dExtraCmd ); /* Get the supported Extra-Commands of a User. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on succes: the user's Extra-Commands concatenated with the bitwise inclusive binary operator "|". -on error: 0. */ unsigned char GetUserExtraCmd( const struct CFtpServer::UserNode *User ) { if( User ) { return User->ucExtraCmd; } else return 0; } #endif /*************************************** * START / STOP ***************************************/ /* Ask the Server to Start Listening on the TCP-Port supplied by SetPort(). Arguments: -the Network Adress CFtpServer will listen on. Example: INADDR_ANY for all local interfaces. inet_addr( "127.0.0.1" ) for the TCP Loopback interface. -the TCP-Port on which CFtpServer will listen. Returns: -on success: true. -on error: false, the supplied Adress or TCP-Port may not be valid. */ bool StartListening( unsigned long ulAddr, unsigned short int usPort ); /* Ask the Server to Stop Listening. Returns: -on success: true. -on error: false. */ bool StopListening( void ); /* Check if the Server is currently Listening. Returns: -true: if the Server is currently listening. -false: if the Server isn't currently listening. */ bool IsListening( void ) { return this->bIsListening; } /* Ask the Server to Start Accepting Clients. Returns: -on success: true. -on error: false. */ bool StartAccepting( void ); /* Check if the Server is currently Accpeting Clients. Returns: -true: if the Server is currently accepting clients. -false: if the Server isn't currently accepting clients. */ bool IsAccepting( void ) { return this->bIsAccepting; } /**************************************** * CONFIG ****************************************/ /* Get the TCP Port on which CFtpServer will listen for incoming clients. Arguments: Returns: -on success: the TCP-Port. -on error: 0. */ unsigned short GetListeningPort( void ) { return this->usPort; } /* Set the TCP Port Range CFtpServer can use to Send and Receive Files or Data. Arguments: -the First Port of the Range. -the Number of Ports, including the First previously given. Returns: -on success: true. -on error: false. */ bool SetDataPortRange( unsigned short int usDataPortStart, unsigned int iNumber ); /* Get the TCP Port Range CFtpServer can use to Send and Receive Files or Data. Arguments: -a Pointer to the First Port. -a Pointer to the Number of Ports, including the First. Returns: -on success: true. -on error: false. */ bool GetDataPortRange( unsigned short int *usDataPortStart, int *iNumber ) { if( usDataPortStart && iNumber ) { *usDataPortStart = this->DataPortRange.usStart; *iNumber = this->DataPortRange.iNumber; return true; } return false; } /* Allow or disallow Anonymous users. Its privilegies will be set to CFtpServer::READFILE | CFtpServer::LIST. Arguments: -true if you want CFtpServer to accept anonymous clients, otherwise false. -the Anonymous User Start Directory. Returns: -on success: true. -on error: false. */ bool AllowAnonymous( bool bDoAllow, const char *szStartPath ); /* Check if Anonymous Users are allowed. Returns: -true: if Anonymous Users are allowed. -false: if Anonymous Users aren't allowed. */ bool IsAnonymousAllowed( void ) { return this->bAllowAnonymous; } #ifdef CFTPSERVER_ANTIBRUTEFORCING /* Set the delay the Server will wait when checking for the Client's pass. */ void SetCheckPassDelay( int iCheckPassDelay ) { this->iCheckPassDelay = iCheckPassDelay; } /* Get the delay the Server will wait when checking for the Client's pass. Returns: -the delay the Server will wait when checking for the Client's pass. */ int GetSetCheckPassDelay( void ) { return this->iCheckPassDelay; } #endif /**************************************** * STATISTICS ****************************************/ /* Get in real-time the number of Clients connected to the Server. Returns: -the current number of Clients connected to the Server. */ int GetNbClient( void ) { return this->iNbClient; } /* Get in real-time the number of existing Users. Returns: -the current number of existing Users. */ int GetNbUser( void ) { return this->iNbUser; } /* Get the number of Clients currently connected using the specified User. Arguments: -a pointer to the CFtpServer::UserNode structure of the User. Returns: -on success: the count of Clients using currently the User. -on error: -1. */ int GetNbClientUsingUser( struct CFtpServer::UserNode *User ) { if( User ) { return User->iNbClient; } else return -1; }
Enjoy!
If you want to check if the server is running, or if a user exists, start the example application and type the following in a command prompt:
telnet localhost 21
USER test
PASS pass
STAT /
QUIT
Of course, assuming that you made the application listen on port 21 (default), and that the user's name you are trying to use is 'test' and its password is 'pass'. If it doesn't write "530 Please login with valid USER and PASS" and you see the user's main directory listing, then it works :).
For example:
> telnet localhost 21
< 220 Browser Ftp Server.
> USER test
< 331 Password required for this user.
> PASS pass
< 230 User Logged In.
> STAT /
< 213-Status follows:
< -rwx------ 1 user group 0 Feb 4 16:26 AUTOEXEC.BAT
< drwx------ 1 user group 0 Feb 5 15:16 BJPrinter
< -rwx------ 1 user group 212 Feb 4 16:20 boot.ini
< -rwx------ 1 user group 4952 Aug 30 2002 Bootfont.bin
< -rwx------ 1 user group 0 Feb 4 16:26 CONFIG.SYS
< drwx------ 1 user group 0 Feb 4 16:31 Documents and
Settings
< -rwx------ 1 user group 1073254400 Feb 5 13:51 hiberfil.sys
< -rwx------ 1 user group 0 Feb 4 16:26 IO.SYS
< -rwx------ 1 user group 0 Feb 4 16:26 MSDOS.SYS
< -rwx------ 1 user group 47564 Aug 3 2004 NTDETECT.COM
< -rwx------ 1 user group 251712 Aug 3 2004 ntldr
< -rwx------ 1 user group 1610612736 Feb 5 13:51 pagefile.sys
< drwx------ 1 user group 0 Feb 5 15:12 Program Files
< drwx------ 1 user group 0 Feb 5 14:09 RECYCLER
< drwx------ 1 user group 0 Feb 4 16:30 System Volume
Information
< drwx------ 1 user group 0 Feb 5 14:04 torrent
< drwx------ 1 user group 0 Feb 4 22:10 UT2004Demo
< drwx------ 1 user group 0 Feb 5 16:19 WINDOWS
< 213 End of status
> QUIT
< 221 Goodbye.
Here, I have added '< ' or '> ' at the beginning of each line in order to make it more understandable: '> ' means 'you send', and '< ' means 'you receive'.
Writing the CFtpServer class was pretty fun. I'm now glad to release it to the community.
BuildPath() and SimplifyPath() have been improved.
BuildPath(), the memory allocated was only 1 byte.
SetPort() has been removed, it caused errors on Win32 due to some SDK. Now, the listening TCP-port need to be set in StartListening().
BuildPath() and SimplifyPath() have been improved, in rapidity and reliability.
GetUserListHead(), GetUserListLast(), GetNextUser(), GetPreviousUser(). AddUser() didn't check if the supplied start path was a directory.
SO_REUSEADDR to improve performance.
CFtpServer compiles without any error under SunOS 5.9 (Thanks to Daniel Fruzynski). CFtpServer compiles and runs under Linux.
SetDataPortRange(): Set the TCP port range the CFtpServer can use to send and receive data.
GetDataPortRange(): Get the TCP port range the CFtpServer can use to send and receive data.
AddUser() didn't check if the user's name already existed. NULL pointer when logged in as anonymous.
SetUserMaxClient().
new' wasn't fully deallocated.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 5 May 2006 Editor: Smitha Vijayan |
Copyright 2005 by Poumailloux Julien Everything else Copyright © CodeProject, 1999-2009 Web20 | Advertise on the Code Project |