Winsock needs to be initialized.
[dftpd.git] / Session.cpp
blob846941d9f1afbb980a4be44f81307a8e50b30292
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 close( m_controlSock );
59 if( m_listenSock != 0 )
61 close( m_listenSock );
64 // Data (RFile) needs to be freed before Filesystem (RFs) on symbian
65 m_data.reset();
66 m_filesystem.reset();
69 SessionPtr Session::Create( int controlSock, const SessionControllerPtr& sessionController, const AuthPtr& auth, const std::string& ip, const ServerWPtr& server )
71 SessionPtr ret( new Session( controlSock, sessionController, auth, ip, server ) );
72 ret->m_this = ret;
74 return ret;
77 void Session::Tick()
79 try
81 try
83 switch( m_state )
85 case S_GREETING:
86 SendGreeting();
87 m_state = S_LOGIN;
88 break;
90 case S_LOGIN:
91 if( AwaitLogin() )
93 m_state = S_PASSWORD;
95 break;
97 case S_PASSWORD:
99 PassState state = AwaitPassword();
101 if( state == PS_LOGGEDIN )
103 m_state = S_READY;
105 else if( state == PS_BADPASS )
107 m_state = S_LOGIN;
109 break;
112 case S_READY:
113 AwaitReady();
114 break;
116 default:
117 break;
120 if( m_data )
122 m_data->Tick();
125 catch( SyntaxError& e )
127 SendSyntaxError();
129 catch( QuitRequested& e )
131 m_control->Write( "221 Bye" );
132 Remove();
134 catch( SessionError& e )
136 g_log->Print( std::string("[Session] ") + boost::lexical_cast<std::string>( m_id ) + " encountered problems (" + strerror( errno ) + ")" );
137 Remove();
140 catch( ConnectionTerminated& e )
142 g_log->Print( std::string("[Session] Connection ") + boost::lexical_cast<std::string>( m_id ) + " terminated" );
143 Remove();
147 void Session::Remove()
149 SessionControllerPtr sessionController = m_sessionController.lock();
150 sessionController->Remove( m_this.lock() );
153 void Session::SendGreeting()
155 ServerPtr server = m_server.lock();
156 const std::list<std::string> welcome = server->GetWelcomeMessage();
158 for( std::list<std::string>::const_iterator it = welcome.begin(); it != welcome.end(); ++it )
160 m_control->Write( "220-" + *it );
163 m_control->Write( "220 Dumb FTP Server ready" );
166 void Session::SendSyntaxError()
168 m_control->Write( "500 Syntax error" );
171 void Session::SendNotLoggedIn()
173 m_control->Write( "530 Not logged in" );
176 void Session::SendDataConnectionBusy()
178 // Is it the correct answer?
179 m_control->Write( "425 File transfer already takes place" );
182 void Session::SendSyst()
184 // Is it even relevant?
185 m_control->Write( "215 UNIX" );
188 bool Session::AwaitLogin()
190 if( m_control->Read() )
192 Command cmd = GetCommand();
194 if( cmd[0] == "USER" )
196 if( cmd.size() != 2 )
198 SendSyntaxError();
199 return false;
202 if( m_auth->Login( cmd[1] ) )
204 m_control->Write( "331 Need password" );
205 m_user = cmd[1];
206 return true;
208 else
210 SendNotLoggedIn();
211 return false;
214 else if( cmd[0] == "QUIT" )
216 throw QuitRequestedException;
218 else
220 SendNotLoggedIn();
224 return false;
227 Session::PassState Session::AwaitPassword()
229 if( m_control->Read() )
231 Command cmd = GetCommand();
233 if( cmd[0] == "PASS" )
235 if( cmd.size() != 2 )
237 SendSyntaxError();
238 return PS_BADPASS;
241 if( m_auth->Password( m_user, cmd[1] ) )
243 m_control->Write( "230 Logged in" );
245 m_filesystem.reset( new Filesystem( m_auth->GetRoot( m_user ) ) );
247 g_log->Print( std::string("[Session] User ") + m_user + " logged in on session " + boost::lexical_cast<std::string>( m_id ) );
249 return PS_LOGGEDIN;
251 else
253 SendNotLoggedIn();
254 return PS_BADPASS;
257 else if( cmd[0] == "QUIT" )
259 throw QuitRequestedException;
261 else
263 SendNotLoggedIn();
264 return PS_BADPASS;
268 return PS_NONE;
271 void Session::AwaitReady()
273 if( m_control->Read() )
275 Command cmd = GetCommand();
277 if( cmd[0] == "QUIT" )
279 throw QuitRequestedException;
281 else if( cmd[0] == "NOOP" )
283 m_control->Write( "200 OK" );
285 else if( cmd[0] == "MODE" )
287 HandleMode( cmd );
289 else if( cmd[0] == "TYPE" )
291 HandleType( cmd );
293 else if( cmd[0] == "STRU" )
295 HandleStru( cmd );
297 else if( cmd[0] == "PWD" )
299 PrintDirectory();
301 else if( cmd[0] == "CWD" )
303 ChangeDirectory( cmd );
305 else if( cmd[0] == "CDUP" )
307 ChangeDirectory( ".." );
309 else if( cmd[0] == "SYST" )
311 SendSyst();
313 else if( cmd[0] == "PORT" )
315 HandlePort( cmd );
317 else if( cmd[0] == "RETR" )
319 HandleRetr( cmd );
321 else if( cmd[0] == "STOR" )
323 HandleStor( cmd );
325 else if( cmd[0] == "ABOR" )
327 HandleAbor();
329 else if( cmd[0] == "LIST" )
331 HandleList( cmd );
333 else if( cmd[0] == "PASV" )
335 HandlePasv( cmd );
337 else if( cmd[0] == "DELE" )
339 HandleDele( cmd );
341 else if( cmd[0] == "MKD" )
343 HandleMkd( cmd );
345 else if( cmd[0] == "RMD" )
347 HandleRmd( cmd );
349 else
351 throw SyntaxErrorException;
356 void Session::HandleMode( const Command& cmd )
358 if( cmd.size() != 2 )
360 throw SyntaxErrorException;
363 std::string param = cmd[1];
364 ToUpper( param );
366 if( param == "S" )
368 m_control->Write( "200 OK" );
370 else if( param == "B" || param == "C" )
372 m_control->Write( "504 Not implemented" );
374 else
376 throw SyntaxErrorException;
380 void Session::HandleType( const Command& cmd )
382 if( cmd.size() < 2 || cmd.size() > 3)
384 throw SyntaxErrorException;
387 std::string param = cmd[1];
388 ToUpper( param );
390 if( param == "A" )
392 if( cmd.size() == 3 )
394 std::string param2 = cmd[2];
395 ToUpper( param2 );
397 if( param2 != "N" )
399 m_control->Write( "504 Not implemented" );
400 return;
404 m_control->Write( "200 OK" );
406 else if( param == "I" )
408 m_control->Write( "200 OK" );
410 else if( param == "E" || param == "L" )
412 m_control->Write( "504 Not implemented" );
414 else
416 throw SyntaxErrorException;
420 void Session::HandleStru( const Command& cmd )
422 if( cmd.size() != 2 )
424 throw SyntaxErrorException;
427 std::string param = cmd[1];
428 ToUpper( param );
430 if( param == "F" )
432 m_control->Write( "200 OK" );
434 else if( param == "R" )
436 throw SessionErrorException;
438 else if( param == "P" )
440 m_control->Write( "504 Not implemented" );
442 else
444 throw SyntaxErrorException;
448 void Session::HandlePort( const Command& cmd )
450 if( cmd.size() != 2 )
452 throw SyntaxErrorException;
455 if( m_listenSock )
457 close( m_listenSock );
458 m_listenSock = 0;
461 PortVector pv = SplitPort( cmd[1] );
462 if( pv.size() != 6 )
464 throw SyntaxErrorException;
467 m_dataAddress = pv[0] + "." + pv[1] + "." + pv[2] + "." + pv[3];
468 m_dataPort = ( boost::lexical_cast<int>( pv[4] ) << 8 ) + boost::lexical_cast<int>( pv[5] );
470 m_control->Write( "200 OK" );
473 void Session::HandleRetr( const Command& cmd )
475 if( m_data )
477 SendDataConnectionBusy();
479 else
481 Upload( cmd );
485 void Session::HandleStor( const Command& cmd )
487 if( m_data )
489 SendDataConnectionBusy();
491 else
493 Download( cmd );
497 void Session::HandleAbor()
499 if( !m_data )
501 m_control->Write( "225 No data connection" );
503 else
505 g_log->Print( std::string("[Session] Data connection aborted on session ") + boost::lexical_cast<std::string>( m_id ) );
507 m_data.reset();
509 m_control->Write( "426 File transfer aborted" );
510 m_control->Write( "226 Data connection closed" );
514 void Session::HandleList( const Command& cmd )
516 std::string path( "." );
518 if( cmd.size() > 1 && cmd[1].size() > 0 && cmd[1][0] != '-' )
520 path = cmd[1];
523 std::list<std::string> list = m_filesystem->GetListing( path );
525 if( list.empty() )
527 m_control->Write( "450 Some problems" );
528 return;
531 m_data.reset( new Data( m_this, list ) );
533 if( !OpenDataConnection() )
535 m_control->Write( "425 Can't open data connection" );
536 m_data.reset();
538 else
540 m_control->Write( std::string( "150 Listing " ) + path );
541 g_log->Print( std::string("[Session] Sending listing on session ") + boost::lexical_cast<std::string>( m_id ) );
545 void Session::HandlePasv( const Command& cmd )
547 if( cmd.size() != 1 )
549 throw SyntaxErrorException;
552 if( !m_listenSock )
554 if( ( m_listenSock = socket( PF_INET, SOCK_STREAM, 0 ) ) == -1 )
556 throw SessionErrorException;
559 sockaddr_in addr;
560 addr.sin_family = AF_INET;
561 addr.sin_port = 0;
562 #ifdef _WIN32
563 addr.sin_addr.s_addr = inet_addr( m_ip.c_str() );
564 #else
565 inet_aton( m_ip.c_str(), &addr.sin_addr );
566 #endif
567 memset( addr.sin_zero, 0, sizeof( addr.sin_zero ) );
569 if( bind( m_listenSock, (sockaddr*)&addr, sizeof( addr ) ) == -1 )
571 throw SessionErrorException;
574 if( listen( m_listenSock, 1 ) == -1 )
576 throw SessionErrorException;
580 sockaddr_in addr;
581 socklen_t len = sizeof( addr );
582 if( getsockname( m_listenSock, (sockaddr*)&addr, &len ) == -1 )
584 throw SessionErrorException;
587 int port = ntohs( addr.sin_port );
588 std::string ip = inet_ntoa( addr.sin_addr );
590 std::replace( ip.begin(), ip.end(), '.', ',' );
592 m_control->Write( std::string( "227 Entering passive mode " ) + ip +
593 "," + boost::lexical_cast<std::string>( port >> 8 ) +
594 "," + boost::lexical_cast<std::string>( port & 0xFF ) );
597 void Session::HandleDele( const Command& cmd )
599 if( cmd.size() != 2 )
601 throw SyntaxErrorException;
604 if( m_filesystem->Delete( cmd[1] ) )
606 m_control->Write( std::string( "250 Deleted " ) + cmd[1] );
608 else
610 m_control->Write( std::string( "550 No access to " ) + cmd[1] + " (" + strerror( errno ) + ")" );
614 void Session::HandleMkd( const Command& cmd )
616 if( cmd.size() != 2 )
618 throw SyntaxErrorException;
621 std::string ret = m_filesystem->MkDir( cmd[1] );
623 if( ret != "" )
625 m_control->Write( std::string( "257 \"" + ret + "\" created" ) );
627 else
629 m_control->Write( "550 Directory not created" );
633 void Session::HandleRmd( const Command& cmd )
635 if( cmd.size() != 2 )
637 throw SyntaxErrorException;
640 if( m_filesystem->RmDir( cmd[1] ) )
642 m_control->Write( "250 OK" );
644 else
646 m_control->Write( "550 No access" );
650 void Session::PrintDirectory()
652 m_control->Write( std::string( "257 " ) + m_filesystem->GetPath() );
655 void Session::ChangeDirectory( const Command& cmd )
657 if( cmd.size() != 2 )
659 throw SyntaxErrorException;
662 ChangeDirectory( cmd[1] );
665 void Session::ChangeDirectory( const std::string& cd )
667 if( m_filesystem->ChangeDirectory( cd ) )
669 m_control->Write( std::string( "200 Changed directory to " ) + m_filesystem->GetPath() );
671 else
673 m_control->Write( "550 Requested action not taken" );
677 void Session::Upload( const Command& cmd )
679 #ifdef SYMBIAN
680 RFile* f;
681 #else
682 FILE* f;
683 #endif
685 if( cmd.size() != 2 )
687 throw SyntaxErrorException;
690 if( !m_filesystem->FileExists( cmd[1] ) )
692 m_control->Write( std::string( "550 File " ) + cmd[1] + " not found" );
693 return;
696 #ifdef SYMBIAN
697 if( ( f = m_filesystem->FileOpenSymbian( cmd[1], Filesystem::M_READ ) ) == NULL )
698 #else
699 if( ( f = m_filesystem->FileOpen( cmd[1], Filesystem::M_READ ) ) == NULL )
700 #endif
702 m_control->Write( std::string( "450 File " ) + cmd[1] + " not accessible" );
703 return;
706 m_data.reset( new Data( m_this, f, Data::M_UPLOAD ) );
708 if( !OpenDataConnection() )
710 m_control->Write( "425 Can't open data connection" );
711 m_data.reset();
713 else
715 m_control->Write( std::string( "150 Sending " ) + cmd[1] );
716 g_log->Print( std::string("[Session] Opened new upload on session ") + boost::lexical_cast<std::string>( m_id ) );
720 void Session::Download( const Command& cmd )
722 #ifdef SYMBIAN
723 RFile* f;
724 #else
725 FILE* f;
726 #endif
728 if( cmd.size() != 2 )
730 throw SyntaxErrorException;
733 #ifdef SYMBIAN
734 if( ( f = m_filesystem->FileOpenSymbian( cmd[1], Filesystem::M_WRITE ) ) == NULL )
735 #else
736 if( ( f = m_filesystem->FileOpen( cmd[1], Filesystem::M_WRITE ) ) == NULL )
737 #endif
739 m_control->Write( std::string( "450 File " ) + cmd[1] + " not accessible" );
740 return;
743 m_data.reset( new Data( m_this, f, Data::M_DOWNLOAD ) );
745 if( !OpenDataConnection() )
747 m_control->Write( "425 Can't open data connection" );
748 m_data.reset();
750 else
752 m_control->Write( std::string( "150 Receiving " ) + cmd[1] );
753 g_log->Print( std::string("[Session] Opened new download on session ") + boost::lexical_cast<std::string>( m_id ) );
757 void Session::DataConnectionFinished()
759 g_log->Print( std::string("[Session] Data connection closed on session ") + boost::lexical_cast<std::string>( m_id ) );
761 m_control->Write( "226 File transfer completed" );
763 m_data.reset();
766 void Session::DataConnectionError()
768 g_log->Print( std::string("[Session] Data connection error on session ") + boost::lexical_cast<std::string>( m_id ) );
770 m_control->Write( "426 Data connection lost" );
772 m_data.reset();
775 void Session::OutOfSpace()
777 g_log->Print( std::string("[Session] Out of space on session ") + boost::lexical_cast<std::string>( m_id ) );
779 m_control->Write( "552 No space left" );
781 m_data.reset();
784 bool Session::OpenDataConnection()
786 bool ok;
788 if( m_listenSock )
790 ok = m_data->Accept( m_listenSock );
791 close( m_listenSock );
792 m_listenSock = 0;
794 else
796 ok = m_data->Connect( m_dataAddress, m_dataPort );
799 return ok;
802 std::list<int> Session::GetFds() const
804 std::list<int> ret;
806 // Server starts by sending greeting, not waiting for data
807 if( m_state == S_GREETING )
809 ret.push_back( -m_controlSock );
811 else
813 ret.push_back( m_controlSock );
816 if( m_listenSock != 0 )
818 ret.push_back( m_listenSock );
821 if( m_data && m_data->GetSock() != 0 )
823 // Hack, mark write descriptors as negative
824 if( m_data->GetMode() == Data::M_DOWNLOAD )
826 ret.push_back( m_data->GetSock() );
828 else // M_UPLOAD, M_LISTING
830 ret.push_back( -m_data->GetSock() );
834 return ret;