Stupid winsock needs special way to close sockets.
[dftpd.git] / Session.cpp
blob6a8953ad969a647fc814340d39b0fe551e8c2962
1 #ifdef _WIN32
2 #include <winsock.h>
3 typedef int socklen_t;
4 #else
5 #include <netinet/in.h>
6 #include <arpa/inet.h>
7 #include <sys/socket.h>
8 #endif
10 #include <iostream>
11 #include <string.h>
12 #include <unistd.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <boost/lexical_cast.hpp>
16 #include "Session.hpp"
17 #include "SessionController.hpp"
18 #include "String.hpp"
19 #include "Exceptions.hpp"
20 #include "Server.hpp"
21 #include "Log.hpp"
23 int Session::m_counter = 0;
25 Session::Session( int controlSock, const SessionControllerPtr& sessionController, const AuthPtr& auth, const std::string& ip, const ServerWPtr& server )
26 : m_control( Telnet::Create( controlSock ) )
27 , m_controlSock( controlSock )
28 , m_listenSock( 0 )
29 , m_dataPort( 20 )
30 , m_id( m_counter++ )
31 , m_state( S_GREETING )
32 , m_sessionController( sessionController )
33 , m_auth( auth )
34 , m_server( server )
35 , m_ip( ip )
37 sockaddr_in addr;
38 socklen_t size = sizeof( sockaddr_in );
40 if( getpeername( m_controlSock, (sockaddr*)&addr, &size ) == -1 )
42 g_log->Print( strerror( errno ) );
43 throw ServerCrashException;
46 m_dataAddress = inet_ntoa( addr.sin_addr );
48 g_log->Print( std::string("[Session] Initializing session ") + boost::lexical_cast<std::string>( m_id ) + " for " + m_dataAddress );
51 Session::~Session()
53 if( m_controlSock != 0 )
55 g_log->Print( std::string("[Session] Closing control socket ") + boost::lexical_cast<std::string>( m_id ) );
56 #ifdef _WIN32
57 closesocket( m_controlSock );
58 #else
59 close( m_controlSock );
60 #endif
63 if( m_listenSock != 0 )
65 #ifdef _WIN32
66 closesocket( m_listenSock );
67 #else
68 close( m_listenSock );
69 #endif
72 // Data (RFile) needs to be freed before Filesystem (RFs) on symbian
73 m_data.reset();
74 m_filesystem.reset();
77 SessionPtr Session::Create( int controlSock, const SessionControllerPtr& sessionController, const AuthPtr& auth, const std::string& ip, const ServerWPtr& server )
79 SessionPtr ret( new Session( controlSock, sessionController, auth, ip, server ) );
80 ret->m_this = ret;
82 return ret;
85 void Session::Tick()
87 try
89 try
91 switch( m_state )
93 case S_GREETING:
94 SendGreeting();
95 m_state = S_LOGIN;
96 break;
98 case S_LOGIN:
99 if( AwaitLogin() )
101 m_state = S_PASSWORD;
103 break;
105 case S_PASSWORD:
107 PassState state = AwaitPassword();
109 if( state == PS_LOGGEDIN )
111 m_state = S_READY;
113 else if( state == PS_BADPASS )
115 m_state = S_LOGIN;
117 break;
120 case S_READY:
121 AwaitReady();
122 break;
124 default:
125 break;
128 if( m_data )
130 m_data->Tick();
133 catch( SyntaxError& e )
135 SendSyntaxError();
137 catch( QuitRequested& e )
139 m_control->Write( "221 Bye" );
140 Remove();
142 catch( SessionError& e )
144 g_log->Print( std::string("[Session] ") + boost::lexical_cast<std::string>( m_id ) + " encountered problems (" + strerror( errno ) + ")" );
145 Remove();
148 catch( ConnectionTerminated& e )
150 g_log->Print( std::string("[Session] Connection ") + boost::lexical_cast<std::string>( m_id ) + " terminated" );
151 Remove();
155 void Session::Remove()
157 SessionControllerPtr sessionController = m_sessionController.lock();
158 sessionController->Remove( m_this.lock() );
161 void Session::SendGreeting()
163 ServerPtr server = m_server.lock();
164 const std::list<std::string> welcome = server->GetWelcomeMessage();
166 for( std::list<std::string>::const_iterator it = welcome.begin(); it != welcome.end(); ++it )
168 m_control->Write( "220-" + *it );
171 m_control->Write( "220 Dumb FTP Server ready" );
174 void Session::SendSyntaxError()
176 m_control->Write( "500 Syntax error" );
179 void Session::SendNotLoggedIn()
181 m_control->Write( "530 Not logged in" );
184 void Session::SendDataConnectionBusy()
186 // Is it the correct answer?
187 m_control->Write( "425 File transfer already takes place" );
190 void Session::SendSyst()
192 // Is it even relevant?
193 m_control->Write( "215 UNIX" );
196 bool Session::AwaitLogin()
198 if( m_control->Read() )
200 Command cmd = GetCommand();
202 if( cmd[0] == "USER" )
204 if( cmd.size() != 2 )
206 SendSyntaxError();
207 return false;
210 if( m_auth->Login( cmd[1] ) )
212 m_control->Write( "331 Need password" );
213 m_user = cmd[1];
214 return true;
216 else
218 SendNotLoggedIn();
219 return false;
222 else if( cmd[0] == "QUIT" )
224 throw QuitRequestedException;
226 else
228 SendNotLoggedIn();
232 return false;
235 Session::PassState Session::AwaitPassword()
237 if( m_control->Read() )
239 Command cmd = GetCommand();
241 if( cmd[0] == "PASS" )
243 if( cmd.size() != 2 )
245 SendSyntaxError();
246 return PS_BADPASS;
249 if( m_auth->Password( m_user, cmd[1] ) )
251 m_control->Write( "230 Logged in" );
253 m_filesystem.reset( new Filesystem( m_auth->GetRoot( m_user ) ) );
255 g_log->Print( std::string("[Session] User ") + m_user + " logged in on session " + boost::lexical_cast<std::string>( m_id ) );
257 return PS_LOGGEDIN;
259 else
261 SendNotLoggedIn();
262 return PS_BADPASS;
265 else if( cmd[0] == "QUIT" )
267 throw QuitRequestedException;
269 else
271 SendNotLoggedIn();
272 return PS_BADPASS;
276 return PS_NONE;
279 void Session::AwaitReady()
281 if( m_control->Read() )
283 Command cmd = GetCommand();
285 if( cmd[0] == "QUIT" )
287 throw QuitRequestedException;
289 else if( cmd[0] == "NOOP" )
291 m_control->Write( "200 OK" );
293 else if( cmd[0] == "MODE" )
295 HandleMode( cmd );
297 else if( cmd[0] == "TYPE" )
299 HandleType( cmd );
301 else if( cmd[0] == "STRU" )
303 HandleStru( cmd );
305 else if( cmd[0] == "PWD" )
307 PrintDirectory();
309 else if( cmd[0] == "CWD" )
311 ChangeDirectory( cmd );
313 else if( cmd[0] == "CDUP" )
315 ChangeDirectory( ".." );
317 else if( cmd[0] == "SYST" )
319 SendSyst();
321 else if( cmd[0] == "PORT" )
323 HandlePort( cmd );
325 else if( cmd[0] == "RETR" )
327 HandleRetr( cmd );
329 else if( cmd[0] == "STOR" )
331 HandleStor( cmd );
333 else if( cmd[0] == "ABOR" )
335 HandleAbor();
337 else if( cmd[0] == "LIST" )
339 HandleList( cmd );
341 else if( cmd[0] == "PASV" )
343 HandlePasv( cmd );
345 else if( cmd[0] == "DELE" )
347 HandleDele( cmd );
349 else if( cmd[0] == "MKD" )
351 HandleMkd( cmd );
353 else if( cmd[0] == "RMD" )
355 HandleRmd( cmd );
357 else
359 throw SyntaxErrorException;
364 void Session::HandleMode( const Command& cmd )
366 if( cmd.size() != 2 )
368 throw SyntaxErrorException;
371 std::string param = cmd[1];
372 ToUpper( param );
374 if( param == "S" )
376 m_control->Write( "200 OK" );
378 else if( param == "B" || param == "C" )
380 m_control->Write( "504 Not implemented" );
382 else
384 throw SyntaxErrorException;
388 void Session::HandleType( const Command& cmd )
390 if( cmd.size() < 2 || cmd.size() > 3)
392 throw SyntaxErrorException;
395 std::string param = cmd[1];
396 ToUpper( param );
398 if( param == "A" )
400 if( cmd.size() == 3 )
402 std::string param2 = cmd[2];
403 ToUpper( param2 );
405 if( param2 != "N" )
407 m_control->Write( "504 Not implemented" );
408 return;
412 m_control->Write( "200 OK" );
414 else if( param == "I" )
416 m_control->Write( "200 OK" );
418 else if( param == "E" || param == "L" )
420 m_control->Write( "504 Not implemented" );
422 else
424 throw SyntaxErrorException;
428 void Session::HandleStru( const Command& cmd )
430 if( cmd.size() != 2 )
432 throw SyntaxErrorException;
435 std::string param = cmd[1];
436 ToUpper( param );
438 if( param == "F" )
440 m_control->Write( "200 OK" );
442 else if( param == "R" )
444 throw SessionErrorException;
446 else if( param == "P" )
448 m_control->Write( "504 Not implemented" );
450 else
452 throw SyntaxErrorException;
456 void Session::HandlePort( const Command& cmd )
458 if( cmd.size() != 2 )
460 throw SyntaxErrorException;
463 if( m_listenSock )
465 close( m_listenSock );
466 m_listenSock = 0;
469 PortVector pv = SplitPort( cmd[1] );
470 if( pv.size() != 6 )
472 throw SyntaxErrorException;
475 m_dataAddress = pv[0] + "." + pv[1] + "." + pv[2] + "." + pv[3];
476 m_dataPort = ( boost::lexical_cast<int>( pv[4] ) << 8 ) + boost::lexical_cast<int>( pv[5] );
478 m_control->Write( "200 OK" );
481 void Session::HandleRetr( const Command& cmd )
483 if( m_data )
485 SendDataConnectionBusy();
487 else
489 Upload( cmd );
493 void Session::HandleStor( const Command& cmd )
495 if( m_data )
497 SendDataConnectionBusy();
499 else
501 Download( cmd );
505 void Session::HandleAbor()
507 if( !m_data )
509 m_control->Write( "225 No data connection" );
511 else
513 g_log->Print( std::string("[Session] Data connection aborted on session ") + boost::lexical_cast<std::string>( m_id ) );
515 m_data.reset();
517 m_control->Write( "426 File transfer aborted" );
518 m_control->Write( "226 Data connection closed" );
522 void Session::HandleList( const Command& cmd )
524 std::string path( "." );
526 if( cmd.size() > 1 && cmd[1].size() > 0 && cmd[1][0] != '-' )
528 path = cmd[1];
531 std::list<std::string> list = m_filesystem->GetListing( path );
533 if( list.empty() )
535 m_control->Write( "450 Some problems" );
536 return;
539 m_data.reset( new Data( m_this, list ) );
541 if( !OpenDataConnection() )
543 m_control->Write( "425 Can't open data connection" );
544 m_data.reset();
546 else
548 m_control->Write( std::string( "150 Listing " ) + path );
549 g_log->Print( std::string("[Session] Sending listing on session ") + boost::lexical_cast<std::string>( m_id ) );
553 void Session::HandlePasv( const Command& cmd )
555 if( cmd.size() != 1 )
557 throw SyntaxErrorException;
560 if( !m_listenSock )
562 if( ( m_listenSock = socket( PF_INET, SOCK_STREAM, 0 ) ) == -1 )
564 throw SessionErrorException;
567 sockaddr_in addr;
568 addr.sin_family = AF_INET;
569 addr.sin_port = 0;
570 #ifdef _WIN32
571 addr.sin_addr.s_addr = inet_addr( m_ip.c_str() );
572 #else
573 inet_aton( m_ip.c_str(), &addr.sin_addr );
574 #endif
575 memset( addr.sin_zero, 0, sizeof( addr.sin_zero ) );
577 if( bind( m_listenSock, (sockaddr*)&addr, sizeof( addr ) ) == -1 )
579 throw SessionErrorException;
582 if( listen( m_listenSock, 1 ) == -1 )
584 throw SessionErrorException;
588 sockaddr_in addr;
589 socklen_t len = sizeof( addr );
590 if( getsockname( m_listenSock, (sockaddr*)&addr, &len ) == -1 )
592 throw SessionErrorException;
595 int port = ntohs( addr.sin_port );
596 std::string ip = inet_ntoa( addr.sin_addr );
598 std::replace( ip.begin(), ip.end(), '.', ',' );
600 m_control->Write( std::string( "227 Entering passive mode " ) + ip +
601 "," + boost::lexical_cast<std::string>( port >> 8 ) +
602 "," + boost::lexical_cast<std::string>( port & 0xFF ) );
605 void Session::HandleDele( const Command& cmd )
607 if( cmd.size() != 2 )
609 throw SyntaxErrorException;
612 if( m_filesystem->Delete( cmd[1] ) )
614 m_control->Write( std::string( "250 Deleted " ) + cmd[1] );
616 else
618 m_control->Write( std::string( "550 No access to " ) + cmd[1] + " (" + strerror( errno ) + ")" );
622 void Session::HandleMkd( const Command& cmd )
624 if( cmd.size() != 2 )
626 throw SyntaxErrorException;
629 std::string ret = m_filesystem->MkDir( cmd[1] );
631 if( ret != "" )
633 m_control->Write( std::string( "257 \"" + ret + "\" created" ) );
635 else
637 m_control->Write( "550 Directory not created" );
641 void Session::HandleRmd( const Command& cmd )
643 if( cmd.size() != 2 )
645 throw SyntaxErrorException;
648 if( m_filesystem->RmDir( cmd[1] ) )
650 m_control->Write( "250 OK" );
652 else
654 m_control->Write( "550 No access" );
658 void Session::PrintDirectory()
660 m_control->Write( std::string( "257 " ) + m_filesystem->GetPath() );
663 void Session::ChangeDirectory( const Command& cmd )
665 if( cmd.size() != 2 )
667 throw SyntaxErrorException;
670 ChangeDirectory( cmd[1] );
673 void Session::ChangeDirectory( const std::string& cd )
675 if( m_filesystem->ChangeDirectory( cd ) )
677 m_control->Write( std::string( "200 Changed directory to " ) + m_filesystem->GetPath() );
679 else
681 m_control->Write( "550 Requested action not taken" );
685 void Session::Upload( const Command& cmd )
687 #ifdef SYMBIAN
688 RFile* f;
689 #else
690 FILE* f;
691 #endif
693 if( cmd.size() != 2 )
695 throw SyntaxErrorException;
698 if( !m_filesystem->FileExists( cmd[1] ) )
700 m_control->Write( std::string( "550 File " ) + cmd[1] + " not found" );
701 return;
704 #ifdef SYMBIAN
705 if( ( f = m_filesystem->FileOpenSymbian( cmd[1], Filesystem::M_READ ) ) == NULL )
706 #else
707 if( ( f = m_filesystem->FileOpen( cmd[1], Filesystem::M_READ ) ) == NULL )
708 #endif
710 m_control->Write( std::string( "450 File " ) + cmd[1] + " not accessible" );
711 return;
714 m_data.reset( new Data( m_this, f, Data::M_UPLOAD ) );
716 if( !OpenDataConnection() )
718 m_control->Write( "425 Can't open data connection" );
719 m_data.reset();
721 else
723 m_control->Write( std::string( "150 Sending " ) + cmd[1] );
724 g_log->Print( std::string("[Session] Opened new upload on session ") + boost::lexical_cast<std::string>( m_id ) );
728 void Session::Download( const Command& cmd )
730 #ifdef SYMBIAN
731 RFile* f;
732 #else
733 FILE* f;
734 #endif
736 if( cmd.size() != 2 )
738 throw SyntaxErrorException;
741 #ifdef SYMBIAN
742 if( ( f = m_filesystem->FileOpenSymbian( cmd[1], Filesystem::M_WRITE ) ) == NULL )
743 #else
744 if( ( f = m_filesystem->FileOpen( cmd[1], Filesystem::M_WRITE ) ) == NULL )
745 #endif
747 m_control->Write( std::string( "450 File " ) + cmd[1] + " not accessible" );
748 return;
751 m_data.reset( new Data( m_this, f, Data::M_DOWNLOAD ) );
753 if( !OpenDataConnection() )
755 m_control->Write( "425 Can't open data connection" );
756 m_data.reset();
758 else
760 m_control->Write( std::string( "150 Receiving " ) + cmd[1] );
761 g_log->Print( std::string("[Session] Opened new download on session ") + boost::lexical_cast<std::string>( m_id ) );
765 void Session::DataConnectionFinished()
767 g_log->Print( std::string("[Session] Data connection closed on session ") + boost::lexical_cast<std::string>( m_id ) );
769 m_control->Write( "226 File transfer completed" );
771 m_data.reset();
774 void Session::DataConnectionError()
776 g_log->Print( std::string("[Session] Data connection error on session ") + boost::lexical_cast<std::string>( m_id ) );
778 m_control->Write( "426 Data connection lost" );
780 m_data.reset();
783 void Session::OutOfSpace()
785 g_log->Print( std::string("[Session] Out of space on session ") + boost::lexical_cast<std::string>( m_id ) );
787 m_control->Write( "552 No space left" );
789 m_data.reset();
792 bool Session::OpenDataConnection()
794 bool ok;
796 if( m_listenSock )
798 ok = m_data->Accept( m_listenSock );
799 close( m_listenSock );
800 m_listenSock = 0;
802 else
804 ok = m_data->Connect( m_dataAddress, m_dataPort );
807 return ok;
810 std::list<int> Session::GetFds() const
812 std::list<int> ret;
814 // Server starts by sending greeting, not waiting for data
815 if( m_state == S_GREETING )
817 ret.push_back( -m_controlSock );
819 else
821 ret.push_back( m_controlSock );
824 if( m_listenSock != 0 )
826 ret.push_back( m_listenSock );
829 if( m_data && m_data->GetSock() != 0 )
831 // Hack, mark write descriptors as negative
832 if( m_data->GetMode() == Data::M_DOWNLOAD )
834 ret.push_back( m_data->GetSock() );
836 else // M_UPLOAD, M_LISTING
838 ret.push_back( -m_data->GetSock() );
842 return ret;