/*
Copyright (C) 2005  Oleg Grigoriev

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "sharescanner.h"

int isconnected( int s, fd_set *rd, fd_set *wr, fd_set * )
{
   int err;
   int len = sizeof( err );;   
   errno = 0;   
   if ( !FD_ISSET( s, rd ) && !FD_ISSET( s, wr ) )   
   { 
    return 0;
   }
   
   if ( getsockopt( s, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len ) < 0 )
    return 0;
   errno = err;
   return err == 0;
}

bool chkport( const char * ipaddress, int connect_timeout, int port_number )
{
   bool pOpen = FALSE;
   int Ms; //SOCKET
   
   fd_set rdevents;
   fd_set wrevents;
   fd_set exevents;
   struct sockaddr_in peer;
   struct timeval tv;
   
   int flags;
   int rc;
   
   bzero( &peer, sizeof( peer ) );
   peer.sin_family = AF_INET;
   peer.sin_port = htons( port_number );
   peer.sin_addr.s_addr = inet_addr( ipaddress );
   
   Ms = socket( AF_INET, SOCK_STREAM, 0 );
   
   if ( !( Ms >= 0 ) ) 
   {
#ifdef QT_DEBUG      
      qDebug( "socket call failed" );
#endif      
      return FALSE;
   }
   int sopts1 = 1;
   setsockopt (Ms, SOL_SOCKET, SO_REUSEADDR, (char *) &sopts1, sizeof (sopts1));
   setsockopt (Ms, SOL_SOCKET, SO_OOBINLINE, (char *) &sopts1, sizeof (sopts1));
   
   if( ( flags = fcntl( Ms, F_GETFL, 0 ) ) < 0 )
      qDebug( "fcntl (F_GETFL) failed" );
   if ( fcntl( Ms, F_SETFL, flags | O_NONBLOCK ) < 0 )
      qDebug( "fcntl (F_SETFL) failed" );
   
   rc = connect( Ms, ( struct sockaddr * )&peer, sizeof( peer ) );
   
   if ( rc == -1 && errno != EINPROGRESS )     
   {            
      qDebug( QString("connect return -1 errno = %1").arg(errno) );   
      pOpen = FALSE;   
   }
   
   else if ( rc == 0  && errno != EINPROGRESS )	/* already connected? */
   {
      // CONNECTED OK!      
#ifdef QT_DEBUG      
      qDebug("connected quickly");  
#endif      
      pOpen = TRUE;
   }
   else
   {   
      FD_ZERO( &rdevents );
      FD_SET( Ms, &rdevents );
      wrevents = rdevents;
      exevents = rdevents;
      tv.tv_sec = 0;
      tv.tv_usec = connect_timeout;
      rc = select( Ms + 1, &rdevents, &wrevents, &exevents, &tv );
      if ( rc < 0 )
      {
#ifdef QT_DEBUG         
         qDebug( "select failed" );
#endif 
      }
      else if ( rc == 0 )
      {
#ifdef QT_DEBUG         
         qDebug( "connect timed out\n" );
#endif 
      }
      else if ( isconnected( Ms, &rdevents, &wrevents, &exevents ) )
      { 
#ifdef QT_DEBUG         
         qDebug("connected 2");      
#endif         
         pOpen = TRUE;
      }      
      else 
      {
#ifdef QT_DEBUG         
         qDebug( "connect failed 2" );
#endif         
      }
   }   
   
   if ( fcntl( Ms, F_SETFL, flags ) < 0 )
      qDebug( "fcntl (F_SETFL) failed" );
   
   shutdown( Ms, SHUT_RDWR);   
   close(Ms);   
   
   return pOpen;
}

// need to know child pid with popen
FILE *popen2(pid_t *childpid, const char *command, const char *mode)
{
   volatile int parent_end, child_end;
   int pipe_fds[2];
   pid_t child_pid;
      
   if ( pipe(pipe_fds) < 0)
      return NULL;
   
   if (mode[0] == 'r' && mode[1] == '\0')
   {
      parent_end = pipe_fds[0];
      child_end = pipe_fds[1];
   }
   else if (mode[0] == 'w' && mode[1] == '\0')
   {
      parent_end = pipe_fds[1];
      child_end = pipe_fds[0];
    }
   else
   {
      close (pipe_fds[0]);
      close (pipe_fds[1]);
      return NULL;
   }
   
   child_pid = fork();
   
   if (child_pid == 0)
   {
      int child_std_end = mode[0] == 'r' ? 1 : 0;
      
      close (parent_end);
      
      if (child_end != child_std_end)
      {
         dup2 (child_end, child_std_end);
         close (child_end);
      }
           
      execl ("/bin/sh", "sh", "-c", command, (char *) 0);
      exit (127);
   }
   
   close (child_end);
   if (child_pid < 0)
   {
      close (parent_end);
      return NULL;
   }
   *childpid = child_pid;
   
   return fdopen(parent_end, mode);
}

/*********************************************************************************/

void ScanThread::stopIt()
{
   //
}


void ScanThread::run()
{
   shares.clear();
   
   //ftp
   if (sFtp) if ( chkport( ipName.latin1(), connect_timeoutNow, FTP_PORT ) ) shares.append( "ftp://" );
   
   //http
   if (sHttp) if ( chkport( ipName.latin1(), connect_timeoutNow, HTTP_PORT ) ) shares.append ("http://");
   
   //smb
   bool PortOpen = chkport( ipName.latin1(), connect_timeoutNow, SMB_PORT ); 
   
   if ( !PortOpen ) // порт закрыт - сразу вышли
   {      
      return;
   }
//-----------------------------------------------------------------------
   QTime  tOut;
   
   QString command = QString(CMD); 
   command.replace( "$I", ipName );
 
   tOut = QTime::currentTime();
   
   //qDebug(".......... scan begin " + command );
   
   pid_t pd=-1;
   FILE *stream1 = popen2(&pd, command.local8Bit() , "r"  );
   
   QCString sr(256);
   
   QString reads;
   
   struct pollfd fdss[1];
   fdss[0].fd = fileno( stream1 );
   fdss[0].events = POLLIN;
   fdss[0].revents = 0;
   
     
   int res = poll( fdss, 1, timeoutNow );
   if ( res > 0 ) 
   {
      if ( fdss[0].revents == POLLIN )
      {
            
         while ( fgets(sr.begin(), 255, stream1 ) != NULL  ) 
         {
            reads = reads + QString::fromLocal8Bit(sr);
         }
         
      }
   }
      //qDebug("stop fgets %d ,ms", tOut.msecsTo( QTime::currentTime() ) );
   
   else // error or timeout
   {
  
   //qDebug("Time out poll stops %d ,ms", tOut.msecsTo( QTime::currentTime() ) );
   kill( pd, SIGKILL );
   //qDebug("Kill %d ,ms", tOut.msecsTo( QTime::currentTime() ) );
   //usleep(10);
   waitpid( pd, 0, 0 );
   //qDebug("waitpid %d ,ms", tOut.msecsTo( QTime::currentTime() ) );  
   }
   
   fclose( stream1 );
   //qDebug("fclose %d ,ms", tOut.msecsTo( QTime::currentTime() ) );
   
   shares += shares.split('\n', reads);
   
   //qDebug("SHARERS LIST = " + shares.join(", ") );
   //qDebug( "Time elapsed fgets: %d ms", timeCount.elapsed() );
   
   //qDebug(".......... done %d ,ms", tOut.msecsTo( QTime::currentTime() ) );
   
}

void ScanThread::setParam( QString ip, int timeout, int connect_to, bool scnFtp, bool scnHttp )
{
   if ( ! ip.isNull() ) 
   {
      if ( timeout > 10 ) timeoutNow = timeout;
      if ( connect_to > 50 ) connect_timeoutNow = connect_to;
      ipName = ip;
      sFtp = scnFtp;
      sHttp = scnHttp;
   }
}    


/************************************************************************************************/

void ShareScanner::scan( QStringList *ipList, int numthreads, int chk_time, int global_chk_time, int connect_timeout, bool sFtp, bool sHttp   )
{

   if ( ipList->count() < 1 ) { qWarning("Error: Empty ipList"); return; }
   if ( numthreads < 1 ) { qWarning("Error: numthreads < 1"); return; }
   
   scanIpList = ipList;
   scanIndex = 0;
   treadTimeout = chk_time;
   cycleTime  = global_chk_time;
   tconnectTimeout = connect_timeout;
   scnFtp = sFtp;
   scnHttp = sHttp;
   
   threads.clear();
   ScanThread *st;
      
   for (int i = 0; i < numthreads; i++ )
   {
      st = new ScanThread();
      threads.append( st );
   }
   
   //qDebug( QString("Info: created %1 threads.").arg( threads.count() ) );
   
   
   scanCycle();
   
}

void ShareScanner::scanCycle()
{
  
   ScanThread *curthread;
   QStringList curShares;
   QString curIp;
   
   bool ALL_DONE = TRUE;
   // пробегаем по всем процессам ALL_DONE = TRUE
   
   for ( curthread = threads.first(); curthread; curthread = threads.next() )
   {   
   
      if ( !curthread->running() )
      {
   //    если get_ip() != "" берем его get_shares()
         curIp = curthread->get_ip();
         //qDebug( QString(">>> scan done = ") + curIp );
         
         if ( curIp != "" )
         {
   //       если get_shares().count() > 0 emit findShare( ip, shares )
            curShares = curthread->get_shares();
            //qDebug( QString(">>> ip = ") + curIp + QString("                     find %1 shares").arg( curShares.count() ) );
             if ( curShares.count() > 0  ) emit findShare( &curIp, &curShares );
         }      
   //    Если scanIndex <  (scanIpList->count() - 1) 
         if ( scanIndex < (scanIpList->count() - 1)  )
         {
   //       забиваем туда новый ip и start() ALL_DONE = FALSE          
            curIp = *scanIpList->at(scanIndex);
            //qDebug( QString(">>>>>> start scaning ip = %1 scanIndex = %2").arg(curIp).arg(scanIndex) );
            curthread->setParam( curIp , treadTimeout, tconnectTimeout, scnFtp, scnHttp );
            curthread->start();
            scanIndex++;
            ALL_DONE = FALSE;
         }
               
      }  
      // Иначе ALL_DONE = FALSE
      else
      {
         ALL_DONE = FALSE;
      }
      
      qApp->processEvents();
      
   }   
   //---------------------------
   // Если (ALL_DONE) emit scanDone(); return;
   if ( ALL_DONE ) 
   {
#ifdef QT_DEBUG      
      qDebug(">>>>>>>>>>>>>>>>>>all scan done");
#endif      
      emit scanDone();
   }
   // иначе запускаем таймер
   else
   {
      emit scanNowIp( &curIp );
      timer.start( cycleTime, TRUE );   
   }
   
   
}

void ShareScanner::timerDone()
{
   scanCycle(); 
}

void ShareScanner::stopScan()
{
   threads.clear();
   scanIpList->clear();
}

