/***************************************************************************
    smb4kscanner.cpp  -  The network scan core class of Smb4K.
                             -------------------
    begin                : Sam Mai 31 2003
    copyright            : (C) 2003-2007 by Alexander Reinholdt
    email                : dustpuppy@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *   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                                                    *
 ***************************************************************************/

// TQt includes
#include <tqapplication.h>
#include <tqmap.h>
#include <tqdeepcopy.h>

// KDE includes
#include <tdelocale.h>
#include <tdeapplication.h>
#include <kdebug.h>
#include <tdesocketaddress.h>

// system includes
#include <stdlib.h>

// Application specific includes.
#include "smb4kscanner.h"
#include "smb4kscanner_p.h"
#include "smb4kauthinfo.h"
#include "smb4kerror.h"
#include "smb4tdeglobal.h"
#include "smb4ksambaoptionshandler.h"
#include "smb4kpasswordhandler.h"
#include "smb4knetworkitems.h"
#include "smb4ksettings.h"

using namespace Smb4TDEGlobal;

static bool created_workgroups_list = false;
static bool created_hosts_list = false;



Smb4KScanner::Smb4KScanner( TQValueList<Smb4KWorkgroupItem *> *workgroups, TQValueList<Smb4KHostItem *> *hosts,
TQObject *parent, const char *name )
: TQObject( parent, name ), m_workgroups_list( workgroups ), m_hosts_list( hosts )
{
  m_priv = new Smb4KScannerPrivate;

  if ( !m_workgroups_list )
  {
    created_workgroups_list = true;
    m_workgroups_list = new TQValueList<Smb4KWorkgroupItem *>;
  }
  else
  {
    // The list was passed to the constructor from outside.
  }

  if ( !m_hosts_list )
  {
    created_hosts_list = true;
    m_hosts_list = new TQValueList<Smb4KHostItem *>;
  }
  else
  {
    // The list was passed to the constructor from outside.
  }

  m_proc = new TDEProcess( this, "ScannerMainProcess" );
  m_proc->setUseShell( true );

  m_working = false;

  m_queue.setAutoDelete( true );

  connect( m_proc, TQ_SIGNAL( receivedStdout( TDEProcess *, char *, int ) ),
           this,   TQ_SLOT( slotReceivedStdout( TDEProcess *, char *, int ) ) );

  connect( m_proc, TQ_SIGNAL( processExited( TDEProcess* ) ),
           this,   TQ_SLOT( slotProcessExited( TDEProcess * ) ) );

  connect( m_proc, TQ_SIGNAL( receivedStderr( TDEProcess *, char *, int ) ),
           this,   TQ_SLOT( slotReceivedStderr( TDEProcess *, char *, int ) ) );
}


Smb4KScanner::~Smb4KScanner()
{
  abort();

  // Delete the list of workgroups, if necessary:
  if ( created_workgroups_list )
  {
    for ( TQValueList<Smb4KWorkgroupItem *>::Iterator it = m_workgroups_list->begin(); it != m_workgroups_list->end(); ++it )
    {
      delete *it;
    }

    m_workgroups_list->clear();

    delete m_workgroups_list;
  }
  else
  {
    // The list of workgroups is handled outside of this class.
  }

  // Delete the list of hosts, if necessary:
  if ( created_hosts_list )
  {
    for ( TQValueList<Smb4KHostItem *>::Iterator it = m_hosts_list->begin(); it != m_hosts_list->end(); ++it )
    {
      delete *it;
    }

    m_hosts_list->clear();

    delete m_hosts_list;
  }
  else
  {
    // The list of hosts is handled outside of this class.
  }

  delete m_priv;
}


void Smb4KScanner::init()
{
  m_timer_id = startTimer( TIMER_INTERVAL );
  rescan();
}


void Smb4KScanner::rescan()
{
  m_queue.enqueue( new TQString( TQString( "%1:" ).arg( Init ) ) );
}


/****************************************************************************
   Scans for workgroup members. (public part)
****************************************************************************/

void Smb4KScanner::getWorkgroupMembers( const TQString &workgroup, const TQString &master, const TQString &ip )
{
  m_queue.enqueue( new TQString( TQString( "%1:%2:%3:%4" ).arg( Hosts ).arg( workgroup, master, ip ) ) );
}


/****************************************************************************
   Scans for shares on a selected host. (public part)
****************************************************************************/

void Smb4KScanner::getShares( const TQString &workgroup, const TQString &host, const TQString &ip, const TQString &protocol )
{
  m_queue.enqueue( new TQString( TQString( "%1:%2:%3:%4:%5" ).arg( Shares ).arg( workgroup, host, ip ).arg( protocol ) ) );
}


/****************************************************************************
   Gets more info on a selected host. (public part)
****************************************************************************/

void Smb4KScanner::getInfo( const TQString &workgroup, const TQString &host, const TQString &ip )
{
  Smb4KHostItem *item = getHost( host, workgroup );

  if ( item && item->infoChecked() )
  {
    emit info( item );

    return;
  }
  else
  {
    // Avoid several queueing up:
    item->setInfoChecked( true );
  }

  m_queue.enqueue( new TQString( TQString( "%1:%2:%3:%4" ).arg( Info ).arg( workgroup, host, ip ) ) );
}


/****************************************************************************
   Searches for a host. (public part)
****************************************************************************/

void Smb4KScanner::search( const TQString &host )
{
  // Check whether we already have this host in
  // the list:
  Smb4KHostItem *item = getHost( host );

  if ( item )
  {
    emit searchResult( item );

    return;
  }

  m_queue.enqueue( new TQString( TQString( "%1:%2" ).arg( Search ).arg( host ) ) );
}


/****************************************************************************
   Aborts any process that is running.
****************************************************************************/

void Smb4KScanner::abort()
{
  m_queue.clear();

  if ( m_proc->isRunning() )
  {
    m_proc->kill();
  }
}


/****************************************************************************
   This function retrieves the initial browse list
****************************************************************************/

void Smb4KScanner::scanNetwork()
{
  abort();

  TQString command;

  // Look up the workgroups/domains and their master browsers.
  // At the moment we have three methods:
  // (1) Smb4KSettings::EnumBrowseList::LookupDomains: This method is
  //     the most reliable one. It uses nmblookup and will only find
  //     *active* workgroup master browsers and thus active domains.
  // (2) Smb4KSettings::EnumBrowseList::QueryCurrentMaster: This
  //     method will query the current master browser of the local
  //     workgroup/domain. This method is not as reliable as the first
  //     one, because you might get wrong (i. e. outdated) master
  //     browsers for the workgroups or even empty workgroups.
  // (3) Smb4KSettings::EnumBrowseList::QueryCustomMaster: This method
  //     is similar to the second one, but the user has defined a fixed
  //     host name or IP address.
  // (4) Smb4KSettings::EnumBrowseList::ScanBroadcastAreas: Scan the
  //     user given broadcast addresses for active hosts (IP scan)

  switch ( Smb4KSettings::browseList() )
  {
    case Smb4KSettings::EnumBrowseList::LookupDomains:
    {
      command.append( "nmblookup -M " );
      command.append( optionsHandler()->nmblookupOptions() );
      command.append( " -- - | grep '<01>' | awk '{print $1}'" );
      command.append( !optionsHandler()->winsServer().isEmpty() ?
                      TQString( " | xargs nmblookup -R -U %1 -A " ).arg( optionsHandler()->winsServer() ) :
                      " | xargs nmblookup -A " );
      command.append( optionsHandler()->nmblookupOptions() );

      *m_proc << command;

      startProcess( Workgroups );

      break;
    }
    case Smb4KSettings::EnumBrowseList::QueryCurrentMaster:
    {
      command.append( "net " );
      command.append( optionsHandler()->netOptions( Smb4KSambaOptionsHandler::LookupMaster,
                                                    Smb4KSettings::domainName() ) );
      command.append( " -U % | xargs net " );
      command.append( optionsHandler()->netOptions( Smb4KSambaOptionsHandler::Domain,
                                                    TQString() ) );
      command.append( " -U % -S" );

      *m_proc << command;

      startProcess( QueryHost );

      break;
    }
    case Smb4KSettings::EnumBrowseList::QueryCustomMaster:
    {
      command.append( "net " );
      command.append( optionsHandler()->netOptions( Smb4KSambaOptionsHandler::LookupHost,
                                                    Smb4KSettings::customMasterBrowser() ) );
      command.append( " -U % -S "+TDEProcess::quote( Smb4KSettings::customMasterBrowser() ) );
      command.append( " | xargs net " );
      command.append( optionsHandler()->netOptions( Smb4KSambaOptionsHandler::Domain,
                                                    TQString() ) );
      command.append( " -U % -S "+TDEProcess::quote( Smb4KSettings::customMasterBrowser() )+" -I " );

      *m_proc << command;

      startProcess( QueryHost );

      break;
    }
    case Smb4KSettings::EnumBrowseList::ScanBroadcastAreas:
    {
      // Get the broadcast addresses that are to be scanned:
      TQStringList addresses = TQStringList::split( ",", Smb4KSettings::broadcastAreas(), false );

      // Build the command:
      for ( TQStringList::ConstIterator it = addresses.begin(); it != addresses.end(); ++it )
      {
        if ( !(*it).isEmpty() )
        {
          command.append( "nmblookup " );
          // We want all globally defined options for nmblookup, except
          // the broadcast address, because that is needed for the IP
          // scan:
          command.append( optionsHandler()->nmblookupOptions( false ) );
          command.append( " -B "+*it+" -- '*' " );
          command.append( "| sed -e /querying/d | awk '{print $1}' " );
          command.append( "| xargs nmblookup " );
          // This time we want to have the globally defined broadcast
          // address:
          command.append( optionsHandler()->nmblookupOptions() );
          // Include the WINS server:
          command.append( !optionsHandler()->winsServer().isEmpty() ?
                          " -R -U "+optionsHandler()->winsServer()+" " : "" );
          command.append( " -A" );
          command.append( " ; " );
          continue;
        }
        else
        {
          continue;
        }
      }

      // Get rid of the last 3 characters (" ; "):
      command.truncate( command.length() - 3 );

      *m_proc << command;

      startProcess( IPScan );

      break;
    }
    default:
    {
      break;
    }
  }
}


/****************************************************************************
   Scans for workgroup members. (private part)
****************************************************************************/

void Smb4KScanner::scanForWorkgroupMembers( const TQString &workgroup, const TQString &master, const TQString &ip )
{
  m_priv->setWorkgroup( workgroup );
  m_priv->setHost( master );
  m_priv->setIP( ip );

  TQString command;

  if ( !ip.isEmpty() )
  {
    command.append( "net "+optionsHandler()->netOptions( Smb4KSambaOptionsHandler::ServerDomain, TQString() ) );
    command.append( " -I "+ip );
    command.append( " -w "+TDEProcess::quote( workgroup ) );
    command.append( " -S "+TDEProcess::quote( master ) );

    Smb4KAuthInfo authInfo( workgroup, master, TQString() );
    (void) passwordHandler()->readAuth( &authInfo );

    if ( !authInfo.user().isEmpty() )
    {
      command.append( TQString( " -U %1" ).arg( TDEProcess::quote( authInfo.user() ) ) );

      if ( !authInfo.password().isEmpty() )
      {
        m_proc->setEnvironment( "PASSWD", authInfo.password() );
      }
    }
    else
    {
      command.append( " -U %" );
    }
  }
  else
  {
    command.append( "net "+optionsHandler()->netOptions( Smb4KSambaOptionsHandler::LookupHost, TDEProcess::quote( master ) ) );
    command.append( " -S "+TDEProcess::quote( master )+" -w "+TDEProcess::quote( workgroup )+" -U % " );
    // FIXME: Maybe we need to know the shell if the user does not use a
    // sh-compatible one...?
    command.append( "| xargs -IIPADDR " );
    command.append( getenv( "SHELL" ) );
    command.append( " -c 'echo \"*** "+master+": IPADDR ***\" && " );
    command.append( "net "+optionsHandler()->netOptions( Smb4KSambaOptionsHandler::ServerDomain, TQString() ) );
    command.append( " -I IPADDR" );
    command.append( " -w "+TDEProcess::quote( workgroup ) );
    command.append( " -S "+TDEProcess::quote( master ) );

    Smb4KAuthInfo authInfo( workgroup, master, TQString() );
    (void) passwordHandler()->readAuth( &authInfo );

    if ( !authInfo.user().isEmpty() )
    {
      command.append( TQString( " -U %1'" ).arg( TDEProcess::quote( authInfo.user() ) ) );

      if ( !authInfo.password().isEmpty() )
      {
        m_proc->setEnvironment( "PASSWD", authInfo.password() );
      }
    }
    else
    {
      command.append( " -U %'" );
    }
  }

  *m_proc << command;

  startProcess( Hosts );
}


/****************************************************************************
   Scans for shares on a selected host. (private part)
****************************************************************************/

void Smb4KScanner::scanForShares( const TQString &workgroup, const TQString &host, const TQString &ip, const TQString &protocol )
{
  m_priv->setWorkgroup( workgroup );
  m_priv->setHost( host );
  m_priv->setIP( ip );

  Smb4KAuthInfo *auth = passwordHandler()->readAuth( new Smb4KAuthInfo( workgroup, host, TQString() ) );

  TQString command;

  command = TQString( "net %1 -w %2 -S %3" ).arg( optionsHandler()->netOptions( Smb4KSambaOptionsHandler::Share, host, protocol ) ).arg( TDEProcess::quote( workgroup ), TDEProcess::quote( host ) );

  if ( !ip.isEmpty() )
  {
    command.append( TQString( " -I %1" ).arg( TDEProcess::quote( ip ) ) );
  }

  if ( !auth->user().isEmpty() )
  {
    command.append( TQString( " -U %1" ).arg( TDEProcess::quote( auth->user() ) ) );

    if ( !auth->password().isEmpty() )
    {
      m_proc->setEnvironment( "PASSWD", auth->password() );
    }
  }
  else
  {
    command.append( " -U guest%" );
  }

  delete auth;

  *m_proc << command;

  startProcess( Shares );
}


/****************************************************************************
   Gets more info on a selected host. (private part)
****************************************************************************/

void Smb4KScanner::scanForInfo( const TQString &workgroup, const TQString &host, const TQString &ip )
{
  m_priv->setWorkgroup( workgroup );
  m_priv->setHost( host );
  m_priv->setIP( ip );

  TQString smbclient_options = optionsHandler()->smbclientOptions();

  TQString command = TQString( "smbclient -d1 -U guest% -W %1 -L %2" ).arg( TDEProcess::quote( workgroup ) ).arg( TDEProcess::quote( host ) );

  if ( !ip.isEmpty() )
  {
    command.append( TQString( " -I %1" ).arg( TDEProcess::quote( ip ) ) );
  }

  if ( !smbclient_options.stripWhiteSpace().isEmpty() )
  {
    command.append( smbclient_options );
  }

  *m_proc << command;

  startProcess( Info );
}


/****************************************************************************
   Searches for a host. (private part)
****************************************************************************/

void Smb4KScanner::searchForHost( const TQString &host )
{
  // We need this because smbclient won't return the host name.
  KNetwork::KIpAddress ip_address = KNetwork::KIpAddress( host );

  if ( Smb4KSettings::searchMethod() == Smb4KSettings::EnumSearchMethod::Smbclient &&
       (ip_address.isIPv4Addr() || ip_address.isIPv6Addr()) )
  {
    Smb4KError::error( ERROR_IP_CANNOT_BE_USED );
    m_working = false;
    emit state( SCANNER_STOP );
    return;
  }

  m_priv->setHost( host );

  TQString wins = optionsHandler()->winsServer();
  TQString nmblookup_options = optionsHandler()->nmblookupOptions();
  TQString smbclient_options = optionsHandler()->smbclientOptions();

  TQString command;

  switch ( Smb4KSettings::searchMethod() )
  {
    case Smb4KSettings::EnumSearchMethod::Nmblookup:
    {
      command = TQString( "nmblookup" );

      if ( !nmblookup_options.stripWhiteSpace().isEmpty() )
      {
        command.append( nmblookup_options );
      }

      if ( host.contains( '.', true ) != 3 )
      {
        if ( !wins.isEmpty() )
        {
          command.append( TQString( " -R -U %1 %2 -S | grep '<00>' | sed -e 's/<00>.*//'" ).arg( wins ).arg( m_priv->host() ) );
        }
        else
        {
          command.append( TQString( " %1 -S | grep '<00>' | sed -e 's/<00>.*//'" ).arg( m_priv->host() ) );
        }
      }
      else
      {
        if ( !wins.isEmpty() )
        {
          command.append( TQString( " -R -U %1 %2 -A | grep '<00>' | sed -e 's/<00>.*//'" ).arg( wins ).arg( m_priv->host() ) );
        }
        else
        {
          command.append( TQString( " %1 -A | grep '<00>' | sed -e 's/<00>.*//'" ).arg( m_priv->host() ) );
        }
      }

      break;
    }
    case Smb4KSettings::EnumSearchMethod::Smbclient:
    {
      command = TQString( "smbclient -d2 -U % -L %1" ).arg( m_priv->host() );

      if ( !smbclient_options.stripWhiteSpace().isEmpty() )
      {
        command.append( smbclient_options );
      }

      break;
    }
    default:
    {
      // Something went wrong. Stop here.
      return;
    }
  }

  *m_proc << command;

  startProcess( Search );
}


/****************************************************************************
   Starts the process of the scanner.
****************************************************************************/

void Smb4KScanner::startProcess( int state )
{
  m_state = state;
  m_buffer = TQString();

  if ( state != Info )
  {
    TQApplication::setOverrideCursor( waitCursor );
  }

  m_proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
}


/****************************************************************************
   End the process and tell, what to do with the data.
****************************************************************************/

void Smb4KScanner::endProcess()
{
  switch ( m_state )
  {
    case Workgroups:
    case QueryHost:
      processWorkgroups();
      break;
    case IPScan:
      processIPScan();
      break;
    case Hosts:
      processWorkgroupMembers();
      break;
    case Shares:
      processShares();
      break;
    case Info:
      processInfo();
      break;
    case Search:
      processSearch();
      break;
    default:
      break;
  }

  m_state = Idle;

  m_priv->clearData();

  TQApplication::restoreOverrideCursor();

  m_proc->clearArguments();

  m_working = false;

  emit state( SCANNER_STOP );
}


/****************************************************************************
   Process the list of workgroups.
****************************************************************************/

void Smb4KScanner::processWorkgroups()
{
  TQStringList list = TQStringList::split( '\n', m_buffer, false );

  for ( TQValueList<Smb4KWorkgroupItem *>::Iterator it = m_workgroups_list->begin(); it != m_workgroups_list->end(); ++it )
  {
    delete *it;
  }

  for ( TQValueList<Smb4KHostItem *>::Iterator it = m_hosts_list->begin(); it != m_hosts_list->end(); ++it )
  {
    delete *it;
  }

  m_workgroups_list->clear();
  m_hosts_list->clear();

  if ( m_state == Workgroups )
  {
    TQString workgroup, master, ip;

    for ( TQStringList::Iterator it = list.begin(); it != list.end(); ++it )
    {
      if ( (*it).stripWhiteSpace().startsWith( "Looking" ) )
      {
        ip = (*it).section( "of", 1, 1 ).stripWhiteSpace();

        continue;
      }
      else if ( (*it).contains( "<00>" ) != 0 && (*it).contains( "<GROUP>" ) == 0 )
      {
        if ( workgroup.isEmpty() && master.isEmpty() && !ip.isEmpty() )
        {
          master = (*it).section( "<00>", 0, 0 ).stripWhiteSpace();
        }

        continue;
      }
      else if ( (*it).contains( "<00>" ) != 0 && (*it).contains( "<GROUP>" ) != 0 )
      {
        if ( workgroup.isEmpty() && !master.isEmpty() && !ip.isEmpty() )
        {
          workgroup = (*it).left( (*it).find( "<00>" ) ).stripWhiteSpace();

          m_workgroups_list->append( new Smb4KWorkgroupItem( workgroup, master, ip ) );

          Smb4KHostItem *master_item = new Smb4KHostItem( workgroup, master, TQString(), ip );
          master_item->setMaster( true );

          m_hosts_list->append( master_item );

          workgroup = TQString();
          master = TQString();
          ip = TQString();
        }

        continue;
      }
    }
  }
  else if ( m_state == QueryHost )
  {
    bool process = false;

    for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
    {
      TQString line = (*it).stripWhiteSpace();

      if ( line.startsWith( "-------------" ) )
      {
        process = true;

        continue;
      }

      if ( process && !line.isEmpty() )
      {
        TQString workgroup = line.section( "   ", 0, 0 ).stripWhiteSpace();
        TQString master = line.section( "   ", 1, -1 ).stripWhiteSpace();

        m_workgroups_list->append( new Smb4KWorkgroupItem( workgroup, master, TQString() ) );

        Smb4KHostItem *master_item = new Smb4KHostItem( workgroup, master );
        master_item->setMaster( true );

        m_hosts_list->append( master_item );

        continue;
      }
      else
      {
        continue;
      }
    }
  }

  lookupIPAddresses();

  emit workgroups( *m_workgroups_list );
  emit hostListChanged();
}


/****************************************************************************
  Process the data from the IP range scan
****************************************************************************/

void Smb4KScanner::processIPScan()
{
  TQStringList list = TQStringList::split( '\n', m_buffer, true );

  for ( TQValueList<Smb4KWorkgroupItem *>::Iterator it = m_workgroups_list->begin(); it != m_workgroups_list->end(); ++it )
  {
    delete *it;
  }

  for ( TQValueList<Smb4KHostItem *>::Iterator it = m_hosts_list->begin(); it != m_hosts_list->end(); ++it )
  {
    delete *it;
  }

  m_workgroups_list->clear();
  m_hosts_list->clear();

  // Process the data:
  for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
  {
    if ( (*it).startsWith( "Looking up status of" ) )
    {
      TQString workgroup, host, ip;
      bool master = false;

      // Get the IP address of this host.
      ip = (*it).section( "of", 1, 1 ).stripWhiteSpace();

      // Loop through the data:
      for ( TQStringList::ConstIterator i = it; i != list.end(); ++i )
      {
        if ( (*i).contains( " <00> " ) != 0 )
        {
          if ( (*i).contains( " <GROUP> " ) != 0 )
          {
            workgroup = (*i).section( "<00>", 0, 0 ).stripWhiteSpace();
          }
          else
          {
            host = (*i).section( "<00>", 0, 0 ).stripWhiteSpace();
          }

          continue;
        }
        else if ( (*i).contains( "__MSBROWSE__" ) != 0 && (*i).contains( " <01> " ) != 0 )
        {
          master = true;

          continue;
        }
        else if ( (*i).contains( "MAC Address" ) != 0 || (*i).stripWhiteSpace().isEmpty() )
        {
          it = i;

          break;
        }
        else
        {
          continue;
        }
      }

      if ( !workgroup.isEmpty() )
      {
        Smb4KWorkgroupItem *workgroup_item = getWorkgroup( workgroup );

        if ( workgroup_item )
        {
          if ( master )
          {
            workgroup_item->setMaster( host, ip );
          }
          else
          {
            // Do nothing
          }
        }
        else
        {
          if ( master )
          {
            m_workgroups_list->append( new Smb4KWorkgroupItem( workgroup, host, ip ) );
          }
          else
          {
            m_workgroups_list->append( new Smb4KWorkgroupItem( workgroup, TQString(), TQString() ) );
          }
        }

        Smb4KHostItem *host_item = new Smb4KHostItem( workgroup, host, TQString(), ip );
        host_item->setMaster( master );

        m_hosts_list->append( host_item );
      }
      else
      {
        // Do nothing
      }
    }
    else
    {
      continue;
    }
  }

  // No extra lookup of IP addresses is needed since
  // we searched for IP addresses. :)

  emit workgroups( *m_workgroups_list );
  emit members( m_priv->workgroup(), *m_hosts_list );
  emit hostListChanged();
}


/****************************************************************************
   Process the member list of a workgroup.
****************************************************************************/

void Smb4KScanner::processWorkgroupMembers()
{
  TQStringList list = TQStringList::split( '\n', m_buffer, false );

  switch ( Smb4KSettings::browseList() )
  {
    case Smb4KSettings::EnumBrowseList::LookupDomains:
    case Smb4KSettings::EnumBrowseList::QueryCurrentMaster:
    case Smb4KSettings::EnumBrowseList::QueryCustomMaster:
    {
      if ( m_buffer.contains( "NT_STATUS_ACCESS_DENIED" ) != 0 ||
           m_buffer.contains( "NT_STATUS_LOGON_FAILURE" ) != 0 ||
           m_buffer.contains( "The username or password was not correct" ) != 0 )
      {
        // Authentication failed:
        emit failed();

        if ( passwordHandler()->askpass( m_priv->workgroup(), m_priv->host(),
             TQString(), Smb4KPasswordHandler::AccessDenied,
             kapp->mainWidget() ? kapp->mainWidget() : 0, "AskPass" ) )
        {
           m_queue.enqueue( new TQString( TQString( "%1:%2:%3:%4" ).arg( Hosts ).arg( m_priv->workgroup(), m_priv->host(), m_priv->ip() ) ) );
        }

        return;
      }
      else if ( m_buffer.contains( "Could not connect to server" ) != 0 ||
                m_buffer.contains( "Unable to find a suitable server" ) != 0 ||
                m_buffer.contains( "Invalid ip address specified" ) != 0 )
      {
        // If the IP address is empty, the shell output contains
        // the IP address. Remove it, because it will confuse
        // the user:
        if ( m_priv->ip().isEmpty() )
        {
          list.remove( list.first() );
        }

        // Notify the rest of the program, that something went wrong.
        emit failed();

        // Notify the user:
        Smb4KError::error( ERROR_GETTING_MEMBERS, TQString(), list.join( "\n" ) );

        return;
      }
      else
      {
        // Do nothing
      }

      TQValueList<Smb4KHostItem *> hosts;

      bool process = false;

      for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
      {
        TQString line = (*it).stripWhiteSpace();

        if ( !process )
        {
          // Find the IP address in case we did not know it at the beginning
          // of the scan process and put it into m_priv.ip():
          if ( m_priv->ip().isEmpty() && line.startsWith( "***" ) && line.endsWith( "***" ) )
          {
            m_priv->setIP( line.section( ":", 1, 1 ).section( "***", 0, 0 ).stripWhiteSpace() );

            continue;
          }

          // Find the line after that we will have to process the output
          // to get the servers belonging to this workgroup:
          if ( line.startsWith( "-------------" ) )
          {
            process = true;

            continue;
          }

          continue;
        }
        else
        {
          if ( !line.isEmpty() )
          {
            TQString host, comment;

            if ( line.contains( "   " ) == 0 )
            {
              host = line;
            }
            else
            {
              host = line.section( "   ", 0, 0 ).stripWhiteSpace();
              comment = line.section( "   ", 1, -1 ).stripWhiteSpace();
            }

            Smb4KHostItem *item = new Smb4KHostItem( m_priv->workgroup(), host, comment );

            if ( TQString::compare( item->name(), m_priv->host() ) == 0 )
            {
              // The item is identical to the master browser! Give the
              // respective workgroup item the IP address of the master
              // if it is not already present.
              Smb4KWorkgroupItem *workgroupItem = getWorkgroup( m_priv->workgroup() );

              if ( workgroupItem && workgroupItem->masterIP().isEmpty() )
              {
                workgroupItem->setMasterIP( m_priv->ip() );
              }

              // Set the IP address for new item (we only know it at this point,
              // because this is the workgroup master):
              item->setIPAddress( m_priv->ip() );
              // Set it to be the master:
              item->setMaster( true );
            }

            hosts.append( item );

            continue;
          }
          else
          {
            continue;
          }
        }
      }

      // If the list is empty, put the master in.
      if ( hosts.isEmpty() )
      {
        Smb4KHostItem *item = new Smb4KHostItem( m_priv->workgroup(), m_priv->host() );
        item->setMaster( true );

        hosts.append( item );
      }

      emit members( m_priv->workgroup(), hosts );

      // Now put the hosts in m_hosts_list:
      for ( TQValueList<Smb4KHostItem *>::Iterator it = m_hosts_list->begin(); it != m_hosts_list->end(); ++it )
      {
        if ( TQString::compare( (*it)->workgroup(), m_priv->workgroup() ) == 0 )
        {
          bool found = false;

          for ( TQValueList<Smb4KHostItem *>::Iterator i = hosts.begin(); i != hosts.end(); ++i )
          {
            if ( *i && TQString::compare( (*i)->name(), (*it)->name() ) == 0 )
            {
              found = true;

              // The only thing that might be missing is the comment.
              (*it)->setComment( (*i)->comment() );

              delete *i;
              *i = NULL;

              break;
            }
            else
            {
              continue;
            }
          }

          if ( !found )
          {
            delete *it;
            *it = NULL;
          }
        }
        else
        {
          continue;
        }
      }

      m_hosts_list->remove( NULL );
      hosts.remove( NULL );

      // Append the list:
      *m_hosts_list += hosts;

      // Lookup IP addresses.
      lookupIPAddresses();

      break;
    }
    case Smb4KSettings::EnumBrowseList::ScanBroadcastAreas:
    {
      if ( m_buffer.contains( "NT_STATUS_ACCESS_DENIED" ) != 0 ||
           m_buffer.contains( "NT_STATUS_LOGON_FAILURE" ) != 0 ||
           m_buffer.contains( "The username or password was not correct" ) != 0 )
      {
        // Authentication failed:
        emit failed();

        if ( passwordHandler()->askpass( m_priv->workgroup(), m_priv->host(),
             TQString(), Smb4KPasswordHandler::AccessDenied,
             kapp->mainWidget() ? kapp->mainWidget() : 0, "AskPass" ) )
        {
           m_queue.enqueue( new TQString( TQString( "%1:%2:%3:%4" ).arg( Hosts ).arg( m_priv->workgroup(), m_priv->host(), m_priv->ip() ) ) );
        }

        return;
      }
      else if ( m_buffer.contains( "Could not connect to server" ) != 0 ||
                m_buffer.contains( "Unable to find a suitable server" ) != 0 ||
                m_buffer.contains( "Invalid ip address specified" ) != 0 )
      {
        // We are in IP scan mode, so we can ignore the error and emit
        // what we already have:
        emit members( m_priv->workgroup(), *m_hosts_list );
        emit hostListChanged();

        return;
      }

      // We will not remove any host from the list in IP scan mode,
      // but we will add additional infomation, if available.

      bool process = false;

      for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
      {
        if ( (*it).stripWhiteSpace().startsWith( "-------------" ) )
        {
          process = true;

          continue;
        }

        if ( process && !(*it).stripWhiteSpace().isEmpty() )
        {
          TQString line = (*it).stripWhiteSpace();

          // Extract host name and comment:
          TQString host, comment;

          if ( line.contains( "   " ) == 0 )
          {
            host = line;
          }
          else
          {
            host = line.section( "   ", 0, 0 ).stripWhiteSpace();
            comment = line.section( "   ", 1, -1 ).stripWhiteSpace();
          }

          // Now add the comment to the host item or do nothing if
          // no comment was found:
          if ( !comment.isEmpty() )
          {
            Smb4KHostItem *item = getHost( host, m_priv->workgroup() );

            if ( item )
            {
              item->setComment( comment );
            }

            continue;
          }
          else
          {
            continue;
          }
        }
      }

      emit members( m_priv->workgroup(), *m_hosts_list );

      break;
    }
    default:
    {
      break;
    }
  }

  emit hostListChanged();
}


/****************************************************************************
   Process the share list of a host.
****************************************************************************/

void Smb4KScanner::processShares()
{
  // Error handling
  if ( m_buffer.contains( "The username or password was not correct.", true ) != 0 ||
       m_buffer.contains( "NT_STATUS_ACCOUNT_DISABLED" ) != 0 /* Active Directory error */ )
  {
    // Authentication failed:
    emit failed();

    if ( passwordHandler()->askpass( m_priv->workgroup(), m_priv->host(), TQString(), Smb4KPasswordHandler::AccessDenied, kapp->mainWidget() ? kapp->mainWidget() : 0, "AskPass" ) )
    {
      m_queue.enqueue( new TQString( TQString( "%1:%2:%3:%4:%5" ).arg( Shares ).arg( m_priv->workgroup(), m_priv->host(), m_priv->ip(), TQString() ) ) );
    }

    return;
  }
  else if ( m_buffer.contains( "could not obtain sid for domain", true ) != 0 )
  {
    // FIXME: Does this error only occur when we scan a server that is
    // only capable of the RAP protocol or also under other conditions?
    m_queue.enqueue( new TQString( TQString( "%1:%2:%3:%4:%5" ).arg( Shares ).arg( m_priv->workgroup(), m_priv->host(), m_priv->ip(), "rap" ) ) );

    m_priv->retry = true;

    return;
  }
  else if ( (m_buffer.contains( "Could not connect to server", true ) != 0 &&
             m_buffer.contains( "The username or password was not correct.", true ) == 0) ||
            m_buffer.contains( "Unable to find a suitable server" ) != 0 )
  {
    // We could not get the list of shares:
    emit failed();

    // Notify the user:
    Smb4KError::error( ERROR_GETTING_SHARES, TQString(), m_buffer );

    return;
  }

  TQStringList list = TQStringList::split( '\n', m_buffer, false );

  TQValueList<Smb4KShareItem *> share_list;

  bool process = false;

  for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
  {
    if ( (*it).startsWith( "---" ) )
    {
      process = true;
    }

    if ( process )
    {
      TQString name, type, comment;

      if ( (*it).contains( " Disk     ", true ) != 0 )
      {
        name = (*it).section( " Disk     ", 0, 0 ).stripWhiteSpace();
        type = "Disk";
        comment = (*it).section( " Disk     ", 1, 1 ).stripWhiteSpace();
      }
      else if ( (*it).contains( " Print    ", true ) != 0 )
      {
        name = (*it).section( " Print    ", 0, 0 ).stripWhiteSpace();
        type = "Printer";
        comment = (*it).section( " Print    ", 1, 1 ).stripWhiteSpace();
      }
      else if ( (*it).contains( " IPC      ", true ) != 0 )
      {
        name = (*it).section( " IPC      ", 0, 0 ).stripWhiteSpace();
        type = "IPC";
        comment = (*it).section( " IPC      ", 1, 1 ).stripWhiteSpace();
      }
      else
      {
        continue;
      }

      share_list.append( new Smb4KShareItem( m_priv->workgroup(), m_priv->host(), name, type, comment ) );
    }
    else
    {
      continue;
    }
  }

  emit shares( m_priv->host(), share_list );
}


/****************************************************************************
   Process the search data.
****************************************************************************/

void Smb4KScanner::processSearch()
{
  // NOTE: We do not emit Smb4KScanner::failed() here, because
  // errors are handled in a different way by the search dialog.

  // Stop right here if the user searched for illegal
  // strings like #, ', () etc.
  if ( m_buffer.contains( "Usage:", true ) != 0 ||
       m_buffer.contains( "/bin/sh:", true ) != 0 )
  {
    emit searchResult( new Smb4KHostItem() );

    return;
  }

  TQStringList data = TQStringList::split( '\n', m_buffer.stripWhiteSpace(), false );

  switch ( Smb4KSettings::searchMethod() )
  {
    case Smb4KSettings::EnumSearchMethod::Nmblookup:
    {
      if ( !data.isEmpty() )
      {
        // The last entry in the list is the workgroup:
        TQString workgroup = data.last().stripWhiteSpace();
        TQString host, ip;

        if ( m_priv->host().contains( ".", true ) != 3 )
        {
          // The IP address is in the first entry:
          ip = data.first().stripWhiteSpace().section( " ", 0, 0 );
          // The host.
          host = m_priv->host().upper();
        }
        else
        {
          ip = m_priv->host();
          host = data[0].stripWhiteSpace();
        }

        emit searchResult( new Smb4KHostItem( workgroup, host, TQString(), ip ) );
      }
      else
      {
        emit searchResult( new Smb4KHostItem() );
      }

      break;
    }
    case Smb4KSettings::EnumSearchMethod::Smbclient:
    {
      if ( data.count() > 1 && !data[1].isEmpty() )
      {
        if ( m_buffer.contains( TQString( "Connection to %1 failed" ).arg( m_priv->host() ) ) != 0 )
        {
          emit searchResult( new Smb4KHostItem() );
        }
        else
        {
          TQString workgroup = data.grep( "Domain" ).first().section( "Domain=[", 1, 1 ).section( "]", 0, 0 );
          TQString ip = data.grep( "Got a positive name query" ).first().section( "(", 1, 1 ).section( ")", 0, 0 ).stripWhiteSpace();

          emit searchResult( new Smb4KHostItem( workgroup, m_priv->host().upper(), TQString(), ip ) );
        }
      }
      else
      {
        emit searchResult( new Smb4KHostItem() );
      }

      break;
    }
    default:
    {
      break;
    }
  }
}


/****************************************************************************
   Process the information about a host.
****************************************************************************/

void Smb4KScanner::processInfo()
{
  if ( m_proc->normalExit() )
  {
    TQStringList list = TQStringList::split( '\n', m_buffer, false );

    Smb4KHostItem *host = getHost( m_priv->host(), m_priv->workgroup() );

    if ( host )
    {
      for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
      {
        if ( (*it).stripWhiteSpace().startsWith( "Domain" ) || (*it).stripWhiteSpace().startsWith( "OS" ) )
        {
          // The OS string.
          host->setOSString( (*it).section( "OS=[", 1, 1 ).section( "]", 0, 0 ).stripWhiteSpace() );

          // The Server string.
          host->setServerString( (*it).section( "Server=[", 1, 1 ).section( "]", 0, 0 ).stripWhiteSpace() );

          break;
        }
        else if ( (*it).contains( "Connection to", true ) != 0 )
        {
          // The lookup of the info failed:
          emit failed();

          break;
        }
      }

      emit info( host );
    }
  }
  else
  {
    // In case the process was aborted, we need to enable checking
    // again:
    Smb4KHostItem *host = getHost( m_priv->host(), m_priv->workgroup() );

    if ( host )
    {
      host->setInfoChecked( false );
    }
  }
}


/****************************************************************************
   Get a workgroup item out of the workgroup list.
****************************************************************************/

Smb4KWorkgroupItem *Smb4KScanner::getWorkgroup( const TQString &workgroup )
{
  TQValueListIterator<Smb4KWorkgroupItem *> it;

  for ( it = m_workgroups_list->begin(); it != m_workgroups_list->end(); ++it )
  {
    if ( TQString::compare( (*it)->name(), workgroup ) == 0 )
    {
      break;
    }
    else
    {
      continue;
    }
  }

  return it == m_workgroups_list->end() ? NULL : *it;
}


/****************************************************************************
   Get a workgroup item out of the workgroup list.
****************************************************************************/

Smb4KHostItem *Smb4KScanner::getHost( const TQString &name, const TQString &workgroup )
{
  TQValueListIterator<Smb4KHostItem *> it;

  for ( it = m_hosts_list->begin(); it != m_hosts_list->end(); ++it )
  {
    if ( !workgroup.stripWhiteSpace().isEmpty() &&
         TQString::compare( (*it)->workgroup().upper(), workgroup.upper() ) != 0 )
    {
      continue;
    }

    if ( TQString::compare( (*it)->name().upper(), name.upper() ) == 0 )
    {
      break;
    }
    else
    {
      continue;
    }
  }

  return it == m_hosts_list->end() ? NULL : *it;
}


/****************************************************************************
   Add a host to the host list
****************************************************************************/

void Smb4KScanner::insertHost( Smb4KHostItem *host )
{
  if ( host && !getHost( host->name(), host->workgroup() ) )
  {
    // Use the copy constructor here, so that we do not run into
    // trouble when/if host is deleted.
    Smb4KHostItem *host_item = new Smb4KHostItem( *host );

    m_hosts_list->append( host_item );

    // Check if the workgroup is already known. If not, create a new Smb4KWorkgroupItem,
    // declare the host a pseudo master and add the workgroup to the list.
    if ( !getWorkgroup( host_item->workgroup() ) )
    {
      Smb4KWorkgroupItem *workgroup_item = new Smb4KWorkgroupItem( host_item->workgroup(),
                                           host_item->name(), host_item->ip() );
      workgroup_item->setPseudoMaster();
      host_item->setMaster( true );  // pseudo master

      appendWorkgroup( workgroup_item );
    }

    // Lookup at least the IP address of this host, if necessary:
    if ( host_item->ip().isEmpty() )
    {
      lookupIPAddresses();
    }

    emit hostAdded( host_item );

    emit hostListChanged();
  }
}


/****************************************************************************
   Appends an item to the list of workgroups.
****************************************************************************/

void Smb4KScanner::appendWorkgroup( Smb4KWorkgroupItem *item )
{
  if ( !getWorkgroup( item->name() ) )
  {
    m_workgroups_list->append( item );

    emit workgroups( *m_workgroups_list );
  }
}


void Smb4KScanner::timerEvent( TQTimerEvent * )
{
  // Look for the thing to do (next).
  // At this point, the topmost item will not be
  // dequeued. This will be done below.
  int todo = Idle;
  TQString *head = NULL;

  if ( (head = m_queue.head()) != 0 )
  {
    todo = head->section( ":", 0, 0 ).toInt();
  }

  if ( !m_working && !m_queue.isEmpty() )
  {
    // Start processing with dequeueing the item:
    TQString *item = m_queue.dequeue();

    // Tell the program, that the scanner is running.
    m_working = true;

    switch ( todo )
    {
      case Init:
      {
        emit state( SCANNER_INIT );
        scanNetwork();
        break;
      }
      case Hosts:
      {
        emit state( SCANNER_OPENING_WORKGROUP );
        scanForWorkgroupMembers( item->section( ":", 1, 1 ), item->section( ":", 2, 2 ), item->section( ":", 3, 3 ) );
        break;
      }
      case Shares:
      {
        if ( !m_priv->retry )
        {
          emit state( SCANNER_OPENING_HOST );
        }
        else
        {
          emit state( SCANNER_RETRYING_OPENING_HOST );
          m_priv->retry = false;
        }
        scanForShares( item->section( ":", 1, 1 ), item->section( ":", 2, 2 ), item->section( ":", 3, 3 ), item->section( ":", 4, 4 ) );
        break;
      }
      case Info:
      {
        emit state( SCANNER_RETRIEVING_INFO );
        scanForInfo( item->section( ":", 1, 1 ), item->section( ":", 2, 2 ), item->section( ":", 3, 3 ) );
        break;
      }
      case Search:
      {
        emit state( SCANNER_SEARCHING );
        searchForHost( item->section( ":", 1, 1 ) );
        break;
      }
      default:
        break;
    }

    delete item;
  }
}


/////////////////////////////////////////////////////////////////////////////
// SLOT IMPLEMENTATIONS
/////////////////////////////////////////////////////////////////////////////


/****************************************************************************
   Internal slots.
****************************************************************************/

void Smb4KScanner::slotReceivedStdout( TDEProcess *, char *buf, int len )
{
  m_buffer.append( TQString::fromLocal8Bit( buf, len ) );
}


void Smb4KScanner::slotProcessExited( TDEProcess * )
{
  endProcess();
}


void Smb4KScanner::slotReceivedStderr( TDEProcess *, char  *buf, int len )
{
  m_buffer.append( TQString::fromLocal8Bit( buf, len ) );
}



/***************************************************************************
 *                                                                         *
 *  Lookup for IP addresses                                                *
 *                                                                         *
 ***************************************************************************/

/****************************************************************************
   Start the scanning for IP addresses
****************************************************************************/

void Smb4KScanner::lookupIPAddresses()
{
  bool start = false;
  TQString command = TQString();

  for ( TQValueList<Smb4KHostItem *>::ConstIterator it = m_hosts_list->begin(); it != m_hosts_list->end(); ++it )
  {
    if ( (*it)->ip().stripWhiteSpace().isEmpty() && !(*it)->ipAddressChecked() )
    {
      if ( !start )
      {
        start = true;
      }

      (*it)->setIPAddressChecked( true );

      command.append( "nmblookup" );
      command.append( optionsHandler()->nmblookupOptions() );
      command.append( optionsHandler()->winsServer().isEmpty() ? "" : " -R -U "+TDEProcess::quote( optionsHandler()->winsServer() ) );
      command.append( " -- "+TDEProcess::quote( (*it)->name() )+" | grep '<00>'" );
      command.append( " ; " );

      continue;
    }
    else
    {
      continue;
    }
  }

  command.truncate( command.length() - 3 );

  if ( start )
  {
    TDEProcess *proc = new TDEProcess( this );
    proc->setUseShell( true );

    connect( proc, TQ_SIGNAL( receivedStdout( TDEProcess *, char *, int ) ),
             this, TQ_SLOT( slotReceivedIPAddresses( TDEProcess *, char *, int ) ) );
    connect( proc, TQ_SIGNAL( processExited( TDEProcess * ) ),
             this, TQ_SLOT( slotIPAddressProcessExited( TDEProcess * ) ) );

    *proc << command;
    proc->start( TDEProcess::NotifyOnExit, TDEProcess::Stdout );
  }
}


/****************************************************************************
   Processes IP addresses if data occurrs on stdout.
****************************************************************************/

void Smb4KScanner::slotReceivedIPAddresses( TDEProcess *, char *buf, int len )
{
  // WARNING: Do not implement error handling here!!!

  TQString buffer = TQString::fromLocal8Bit( buf, len );

  if ( !buffer.stripWhiteSpace().isEmpty() )
  {
    TQString ip = buffer.stripWhiteSpace().section( " ", 0, 0 ).stripWhiteSpace();
    TQString host = buffer.stripWhiteSpace().section( " ", 1, 1 ).section( "<00>", 0, 0 ).stripWhiteSpace();

    if ( !host.isEmpty() && !ip.isEmpty() )
    {
      Smb4KHostItem *item = getHost( host );

      if ( item )
      {
        item->setIPAddress( ip );

        if ( item->isMaster() )
        {
          Smb4KWorkgroupItem *workgroup = getWorkgroup( item->workgroup() );

          if ( workgroup )
          {
            workgroup->setMasterIP( ip );
          }
        }

        emit ipAddress( item );
      }
    }
  }
}


/****************************************************************************
  End IP address scan.
****************************************************************************/

void Smb4KScanner::slotIPAddressProcessExited( TDEProcess *p )
{
  delete p;
}


#include "smb4kscanner.moc"
