Add FTP protocol debugging facilities.
[dftpd.git] / Session.cpp
blob74686ff2332ae841b0df98ba20abe510d4a4c141
1 #include <iostream>
2 #include <string.h>
3 #include <unistd.h>
4 #include <errno.h>
5 #include <fcntl.h>
6 #include <netinet/in.h>
7 #include <arpa/inet.h>
8 #include <sys/socket.h>
9 #include <boost/lexical_cast.hpp>
10 #include "Session.hpp"
11 #include "SessionController.hpp"
12 #include "String.hpp"
13 #include "Exceptions.hpp"
14 #include "Server.hpp"
15 #include "Log.hpp"
17 int Session::m_counter = 0;
19 Session::Session( int controlSock, const SessionControllerPtr& sessionController, const AuthPtr& auth, const std::string& ip, const ServerWPtr& server )
20 : m_control( Telnet::Create( controlSock ) )
21 , m_controlSock( controlSock )
22 , m_listenSock( 0 )
23 , m_dataPort( 20 )
24 , m_id( m_counter++ )
25 , m_state( S_GREETING )
26 , m_sessionController( sessionController )
27 , m_auth( auth )
28 , m_server( server )
29 , m_ip( ip )
31 sockaddr_in addr;
32 socklen_t size = sizeof( sockaddr_in );
34 if( getpeername( m_controlSock, (sockaddr*)&addr, &size ) == -1 )
36 g_log->Print( strerror( errno ) );
37 throw ServerCrashException;
40 m_dataAddress = inet_ntoa( addr.sin_addr );
42 g_log->Print( std::string("[Session] Initializing session ") + boost::lexical_cast<std::string>( m_id ) + " for " + m_dataAddress );
45 Session::~Session()
47 if( m_controlSock != 0 )
49 g_log->Print( std::string("[Session] Closing control socket ") + boost::lexical_cast<std::string>( m_id ) );
50 close( m_controlSock );
53 if( m_listenSock != 0 )
55 close( m_listenSock );
58 // Data (RFile) needs to be freed before Filesystem (RFs) on symbian
59 m_data.reset();
60 m_filesystem.reset();
63 SessionPtr Session::Create( int controlSock, const SessionControllerPtr& sessionController, const AuthPtr& auth, const std::string& ip, const ServerWPtr& server )
65 SessionPtr ret( new Session( controlSock, sessionController, auth, ip, server ) );
66 ret->m_this = ret;
68 return ret;
71 void Session::Tick()
73 try
75 try
77 switch( m_state )
79 case S_GREETING:
80 SendGreeting();
81 m_state = S_LOGIN;
82 break;
84 case S_LOGIN:
85 if( AwaitLogin() )
87 m_state = S_PASSWORD;
89 break;
91 case S_PASSWORD:
93 PassState state = AwaitPassword();
95 if( state == PS_LOGGEDIN )
97 m_state = S_READY;
99 else if( state == PS_BADPASS )
101 m_state = S_LOGIN;
103 break;
106 case S_READY:
107 AwaitReady();
108 break;
110 default:
111 break;
114 if( m_data )
116 m_data->Tick();
119 catch( SyntaxError& e )
121 SendSyntaxError();
123 catch( QuitRequested& e )
125 m_control->Write( "221 Bye" );
126 Remove();
128 catch( SessionError& e )
130 g_log->Print( std::string("[Session] ") + boost::lexical_cast<std::string>( m_id ) + " encountered problems (" + strerror( errno ) + ")" );
131 Remove();
134 catch( ConnectionTerminated& e )
136 g_log->Print( std::string("[Session] Connection ") + boost::lexical_cast<std::string>( m_id ) + " terminated" );
137 Remove();
141 void Session::Remove()
143 SessionControllerPtr sessionController = m_sessionController.lock();
144 sessionController->Remove( m_this.lock() );
147 void Session::SendGreeting()
149 ServerPtr server = m_server.lock();
150 const std::list<std::string> welcome = server->GetWelcomeMessage();
152 for( std::list<std::string>::const_iterator it = welcome.begin(); it != welcome.end(); ++it )
154 m_control->Write( "220-" + *it );
157 m_control->Write( "220 Dumb FTP Server ready" );
160 void Session::SendSyntaxError()
162 m_control->Write( "500 Syntax error" );
165 void Session::SendNotLoggedIn()
167 m_control->Write( "530 Not logged in" );
170 void Session::SendDataConnectionBusy()
172 // Is it the correct answer?
173 m_control->Write( "425 File transfer already takes place" );
176 void Session::SendSyst()
178 // Is it even relevant?
179 m_control->Write( "215 UNIX" );
182 bool Session::AwaitLogin()
184 if( m_control->Read() )
186 Command cmd = GetCommand();
188 if( cmd[0] == "USER" )
190 if( cmd.size() != 2 )
192 SendSyntaxError();
193 return false;
196 if( m_auth->Login( cmd[1] ) )
198 m_control->Write( "331 Need password" );
199 m_user = cmd[1];
200 return true;
202 else
204 SendNotLoggedIn();
205 return false;
208 else if( cmd[0] == "QUIT" )
210 throw QuitRequestedException;
212 else
214 SendNotLoggedIn();
218 return false;
221 Session::PassState Session::AwaitPassword()
223 if( m_control->Read() )
225 Command cmd = GetCommand();
227 if( cmd[0] == "PASS" )
229 if( cmd.size() != 2 )
231 SendSyntaxError();
232 return PS_BADPASS;
235 if( m_auth->Password( m_user, cmd[1] ) )
237 m_control->Write( "230 Logged in" );
239 m_filesystem.reset( new Filesystem( m_auth->GetRoot( m_user ) ) );
241 g_log->Print( std::string("[Session] User ") + m_user + " logged in on session " + boost::lexical_cast<std::string>( m_id ) );
243 return PS_LOGGEDIN;
245 else
247 SendNotLoggedIn();
248 return PS_BADPASS;
251 else if( cmd[0] == "QUIT" )
253 throw QuitRequestedException;
255 else
257 SendNotLoggedIn();
258 return PS_BADPASS;
262 return PS_NONE;
265 void Session::AwaitReady()
267 if( m_control->Read() )
269 Command cmd = GetCommand();
271 if( cmd[0] == "QUIT" )
273 throw QuitRequestedException;
275 else if( cmd[0] == "NOOP" )
277 m_control->Write( "200 OK" );
279 else if( cmd[0] == "MODE" )
281 HandleMode( cmd );
283 else if( cmd[0] == "TYPE" )
285 HandleType( cmd );
287 else if( cmd[0] == "STRU" )
289 HandleStru( cmd );
291 else if( cmd[0] == "PWD" )
293 PrintDirectory();
295 else if( cmd[0] == "CWD" )
297 ChangeDirectory( cmd );
299 else if( cmd[0] == "CDUP" )
301 ChangeDirectory( ".." );
303 else if( cmd[0] == "SYST" )
305 SendSyst();
307 else if( cmd[0] == "PORT" )
309 HandlePort( cmd );
311 else if( cmd[0] == "RETR" )
313 HandleRetr( cmd );
315 else if( cmd[0] == "STOR" )
317 HandleStor( cmd );
319 else if( cmd[0] == "ABOR" )
321 HandleAbor();
323 else if( cmd[0] == "LIST" )
325 HandleList( cmd );
327 else if( cmd[0] == "PASV" )
329 HandlePasv( cmd );
331 else if( cmd[0] == "DELE" )
333 HandleDele( cmd );
335 else if( cmd[0] == "MKD" )
337 HandleMkd( cmd );
339 else if( cmd[0] == "RMD" )
341 HandleRmd( cmd );
343 else
345 throw SyntaxErrorException;
350 void Session::HandleMode( const Command& cmd )
352 if( cmd.size() != 2 )
354 throw SyntaxErrorException;
357 std::string param = cmd[1];
358 ToUpper( param );
360 if( param == "S" )
362 m_control->Write( "200 OK" );
364 else if( param == "B" || param == "C" )
366 m_control->Write( "504 Not implemented" );
368 else
370 throw SyntaxErrorException;
374 void Session::HandleType( const Command& cmd )
376 if( cmd.size() < 2 || cmd.size() > 3)
378 throw SyntaxErrorException;
381 std::string param = cmd[1];
382 ToUpper( param );
384 if( param == "A" )
386 if( cmd.size() == 3 )
388 std::string param2 = cmd[2];
389 ToUpper( param2 );
391 if( param2 != "N" )
393 m_control->Write( "504 Not implemented" );
394 return;
398 m_control->Write( "200 OK" );
400 else if( param == "I" )
402 m_control->Write( "200 OK" );
404 else if( param == "E" || param == "L" )
406 m_control->Write( "504 Not implemented" );
408 else
410 throw SyntaxErrorException;
414 void Session::HandleStru( const Command& cmd )
416 if( cmd.size() != 2 )
418 throw SyntaxErrorException;
421 std::string param = cmd[1];
422 ToUpper( param );
424 if( param == "F" )
426 m_control->Write( "200 OK" );
428 else if( param == "R" )
430 throw SessionErrorException;
432 else if( param == "P" )
434 m_control->Write( "504 Not implemented" );
436 else
438 throw SyntaxErrorException;
442 void Session::HandlePort( const Command& cmd )
444 if( cmd.size() != 2 )
446 throw SyntaxErrorException;
449 if( m_listenSock )
451 close( m_listenSock );
452 m_listenSock = 0;
455 PortVector pv = SplitPort( cmd[1] );
456 if( pv.size() != 6 )
458 throw SyntaxErrorException;
461 m_dataAddress = pv[0] + "." + pv[1] + "." + pv[2] + "." + pv[3];
462 m_dataPort = ( boost::lexical_cast<int>( pv[4] ) << 8 ) + boost::lexical_cast<int>( pv[5] );
464 m_control->Write( "200 OK" );
467 void Session::HandleRetr( const Command& cmd )
469 if( m_data )
471 SendDataConnectionBusy();
473 else
475 Upload( cmd );
479 void Session::HandleStor( const Command& cmd )
481 if( m_data )
483 SendDataConnectionBusy();
485 else
487 Download( cmd );
491 void Session::HandleAbor()
493 if( !m_data )
495 m_control->Write( "225 No data connection" );
497 else
499 g_log->Print( std::string("[Session] Data connection aborted on session ") + boost::lexical_cast<std::string>( m_id ) );
501 m_data.reset();
503 m_control->Write( "426 File transfer aborted" );
504 m_control->Write( "226 Data connection closed" );
508 void Session::HandleList( const Command& cmd )
510 std::string path( "." );
512 if( cmd.size() > 1 && cmd[1].size() > 0 && cmd[1][0] != '-' )
514 path = cmd[1];
517 std::list<std::string> list = m_filesystem->GetListing( path );
519 if( list.empty() )
521 m_control->Write( "450 Some problems" );
522 return;
525 m_data.reset( new Data( m_this, list ) );
527 if( !OpenDataConnection() )
529 m_control->Write( "425 Can't open data connection" );
530 m_data.reset();
532 else
534 m_control->Write( std::string( "150 Listing " ) + path );
535 g_log->Print( std::string("[Session] Sending listing on session ") + boost::lexical_cast<std::string>( m_id ) );
539 void Session::HandlePasv( const Command& cmd )
541 if( cmd.size() != 1 )
543 throw SyntaxErrorException;
546 if( !m_listenSock )
548 if( ( m_listenSock = socket( PF_INET, SOCK_STREAM, 0 ) ) == -1 )
550 throw SessionErrorException;
553 sockaddr_in addr;
554 addr.sin_family = AF_INET;
555 addr.sin_port = 0;
556 inet_aton( m_ip.c_str(), &addr.sin_addr );
557 memset( addr.sin_zero, 0, sizeof( addr.sin_zero ) );
559 if( bind( m_listenSock, (sockaddr*)&addr, sizeof( addr ) ) == -1 )
561 throw SessionErrorException;
564 if( listen( m_listenSock, 1 ) == -1 )
566 throw SessionErrorException;
570 sockaddr_in addr;
571 socklen_t len = sizeof( addr );
572 if( getsockname( m_listenSock, (sockaddr*)&addr, &len ) == -1 )
574 throw SessionErrorException;
577 int port = ntohs( addr.sin_port );
578 std::string ip = inet_ntoa( addr.sin_addr );
580 std::replace( ip.begin(), ip.end(), '.', ',' );
582 m_control->Write( std::string( "227 Entering passive mode " ) + ip +
583 "," + boost::lexical_cast<std::string>( port >> 8 ) +
584 "," + boost::lexical_cast<std::string>( port & 0xFF ) );
587 void Session::HandleDele( const Command& cmd )
589 if( cmd.size() != 2 )
591 throw SyntaxErrorException;
594 if( m_filesystem->Delete( cmd[1] ) )
596 m_control->Write( std::string( "250 Deleted " ) + cmd[1] );
598 else
600 m_control->Write( std::string( "550 No access to " ) + cmd[1] + " (" + strerror( errno ) + ")" );
604 void Session::HandleMkd( const Command& cmd )
606 if( cmd.size() != 2 )
608 throw SyntaxErrorException;
611 std::string ret = m_filesystem->MkDir( cmd[1] );
613 if( ret != "" )
615 m_control->Write( std::string( "257 \"" + ret + "\" created" ) );
617 else
619 m_control->Write( "550 Directory not created" );
623 void Session::HandleRmd( const Command& cmd )
625 if( cmd.size() != 2 )
627 throw SyntaxErrorException;
630 if( m_filesystem->RmDir( cmd[1] ) )
632 m_control->Write( "250 OK" );
634 else
636 m_control->Write( "550 No access" );
640 void Session::PrintDirectory()
642 m_control->Write( std::string( "257 " ) + m_filesystem->GetPath() );
645 void Session::ChangeDirectory( const Command& cmd )
647 if( cmd.size() != 2 )
649 throw SyntaxErrorException;
652 ChangeDirectory( cmd[1] );
655 void Session::ChangeDirectory( const std::string& cd )
657 if( m_filesystem->ChangeDirectory( cd ) )
659 m_control->Write( std::string( "200 Changed directory to " ) + m_filesystem->GetPath() );
661 else
663 m_control->Write( "550 Requested action not taken" );
667 void Session::Upload( const Command& cmd )
669 #ifdef SYMBIAN
670 RFile* f;
671 #else
672 FILE* f;
673 #endif
675 if( cmd.size() != 2 )
677 throw SyntaxErrorException;
680 if( !m_filesystem->FileExists( cmd[1] ) )
682 m_control->Write( std::string( "550 File " ) + cmd[1] + " not found" );
683 return;
686 #ifdef SYMBIAN
687 if( ( f = m_filesystem->FileOpenSymbian( cmd[1], Filesystem::M_READ ) ) == NULL )
688 #else
689 if( ( f = m_filesystem->FileOpen( cmd[1], Filesystem::M_READ ) ) == NULL )
690 #endif
692 m_control->Write( std::string( "450 File " ) + cmd[1] + " not accessible" );
693 return;
696 m_data.reset( new Data( m_this, f, Data::M_UPLOAD ) );
698 if( !OpenDataConnection() )
700 m_control->Write( "425 Can't open data connection" );
701 m_data.reset();
703 else
705 m_control->Write( std::string( "150 Sending " ) + cmd[1] );
706 g_log->Print( std::string("[Session] Opened new upload on session ") + boost::lexical_cast<std::string>( m_id ) );
710 void Session::Download( const Command& cmd )
712 #ifdef SYMBIAN
713 RFile* f;
714 #else
715 FILE* f;
716 #endif
718 if( cmd.size() != 2 )
720 throw SyntaxErrorException;
723 #ifdef SYMBIAN
724 if( ( f = m_filesystem->FileOpenSymbian( cmd[1], Filesystem::M_WRITE ) ) == NULL )
725 #else
726 if( ( f = m_filesystem->FileOpen( cmd[1], Filesystem::M_WRITE ) ) == NULL )
727 #endif
729 m_control->Write( std::string( "450 File " ) + cmd[1] + " not accessible" );
730 return;
733 m_data.reset( new Data( m_this, f, Data::M_DOWNLOAD ) );
735 if( !OpenDataConnection() )
737 m_control->Write( "425 Can't open data connection" );
738 m_data.reset();
740 else
742 m_control->Write( std::string( "150 Receiving " ) + cmd[1] );
743 g_log->Print( std::string("[Session] Opened new download on session ") + boost::lexical_cast<std::string>( m_id ) );
747 void Session::DataConnectionFinished()
749 g_log->Print( std::string("[Session] Data connection closed on session ") + boost::lexical_cast<std::string>( m_id ) );
751 m_control->Write( "226 File transfer completed" );
753 m_data.reset();
756 void Session::DataConnectionError()
758 g_log->Print( std::string("[Session] Data connection error on session ") + boost::lexical_cast<std::string>( m_id ) );
760 m_control->Write( "426 Data connection lost" );
762 m_data.reset();
765 void Session::OutOfSpace()
767 g_log->Print( std::string("[Session] Out of space on session ") + boost::lexical_cast<std::string>( m_id ) );
769 m_control->Write( "552 No space left" );
771 m_data.reset();
774 bool Session::OpenDataConnection()
776 bool ok;
778 if( m_listenSock )
780 ok = m_data->Accept( m_listenSock );
781 close( m_listenSock );
782 m_listenSock = 0;
784 else
786 ok = m_data->Connect( m_dataAddress, m_dataPort );
789 return ok;
792 std::list<int> Session::GetFds() const
794 std::list<int> ret;
796 // Server starts by sending greeting, not waiting for data
797 if( m_state == S_GREETING )
799 ret.push_back( -m_controlSock );
801 else
803 ret.push_back( m_controlSock );
806 if( m_listenSock != 0 )
808 ret.push_back( m_listenSock );
811 if( m_data && m_data->GetSock() != 0 )
813 // Hack, mark write descriptors as negative
814 if( m_data->GetMode() == Data::M_DOWNLOAD )
816 ret.push_back( m_data->GetSock() );
818 else // M_UPLOAD, M_LISTING
820 ret.push_back( -m_data->GetSock() );
824 return ret;