/***************************************************************************
    smb4tdefileio  -  Does file IO operations for Smb4K
                             -------------------
    begin                : Do Jan 1 2004
    copyright            : (C) 2004-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 <tqdir.h>
#include <tqfile.h>
#include <tqtextstream.h>

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

// system specific includes
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <grp.h>
#include <pwd.h>

// application specific includes
#include "smb4tdefileio.h"
#include "smb4kdefs.h"
#include "smb4kerror.h"
#include "smb4tdeglobal.h"
#include "smb4ksettings.h"

using namespace Smb4TDEGlobal;



Smb4KFileIO::Smb4KFileIO( TQObject *parent, const char *name ) : TQObject( parent, name )
{
  m_operation =      NoOperation;
  m_state =          Idle;
  m_error_occurred = false;

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

  connect( m_proc, TQT_SIGNAL( receivedStderr( TDEProcess *, char *, int ) ),
           this,   TQT_SLOT( slotReceivedStderr( TDEProcess *, char *, int ) ) );

  connect( m_proc, TQT_SIGNAL( receivedStdout( TDEProcess *, char *, int ) ),
           this,   TQT_SLOT( slotReceivedStdout( TDEProcess *, char *, int ) ) );

  connect( m_proc, TQT_SIGNAL( processExited( TDEProcess * ) ),
           this,   TQT_SLOT( slotProcessExited( TDEProcess * ) ) );

  connect( kapp,   TQT_SIGNAL( shutDown() ),
           this,   TQT_SLOT( slotShutdown() ) );
}


Smb4KFileIO::~Smb4KFileIO()
{
}


bool Smb4KFileIO::writeSudoers( Smb4KFileIO::Operation operation )
{
  m_operation = operation;
  bool ok = false;

  // Stop here if nothing has changed:
  if ( m_operation == NoOperation )
  {
    emit finished();

    ok = true;

    return ok;
  }

  TQString file = "sudoers";

  if ( createLockFile( file ) )
  {
    // Find the file first:
    TQCString canonical_path = findFile( file );

    if ( !canonical_path.isEmpty() )
    {
      // Stat the file, so that we know that it is safe to
      // read from and write to it and whether we need to
      // ask for the super user's password:
      struct stat buf;

      if ( lstat( canonical_path, &buf ) == -1 )
      {
        int error_number = errno;

        Smb4KError::error( ERROR_GETTING_PERMISSIONS, canonical_path, strerror( error_number ) );

        emit failed();
        emit finished();

        removeLockFile();

        return ok; // false
      }

      // Look for the groups the user is in:
      long ngroups_max;
      ngroups_max = sysconf(_SC_NGROUPS_MAX);

      gid_t list[ngroups_max];

      if ( getgroups( ngroups_max, list ) == -1 )
      {
        int error_number = errno;

        Smb4KError::error( ERROR_GETTING_GIDS, TQString(), strerror( error_number ) );

        emit failed();
        emit finished();

        return ok; // false
      }

      gid_t sup_gid = 65534; // set this to gid 'nobody' for initialization
      bool found_gid = false;
      int i = 0;

      while ( list[i] )
      {
        if ( list[i] == buf.st_gid )
        {
          sup_gid = list[i];
          found_gid = true;
        }

        i++;
      }

      // Error out if the file is irregular.
      // Yes, yes, I know that this is normally done in a different
      // way and that there might be a race here, but, hey, right here
      // I don't care!
      if ( !S_ISREG( buf.st_mode ) || S_ISFIFO( buf.st_mode ) || S_ISLNK( buf.st_mode ) )
      {
        Smb4KError::error( ERROR_FILE_IS_IRREGULAR, canonical_path, TQString() );

        emit failed();
        emit finished();

        removeLockFile();

        return ok; // false
      }

      // Check access rights:
      if ( (buf.st_uid == getuid() && (buf.st_mode & 00600) == (S_IWUSR | S_IRUSR)) /* user */ ||
           (found_gid && buf.st_gid == sup_gid && (buf.st_mode & 00060) == (S_IWGRP | S_IRGRP)) /* group */ ||
           ((buf.st_mode & 00006) == (S_IWOTH | S_IROTH)) /* others */ )
      {
        // The user has read and write access.

        TQFile file ( canonical_path );
        TQStringList contents;

        if ( file.open( IO_ReadWrite ) )
        {
          TQTextStream ts( &file );
          ts.setEncoding( TQTextStream::Locale );

          contents = TQStringList::split( "\n", ts.read(), true );

          bool write = false;

          switch ( m_operation )
          {
            case Insert:
            {
              size_t hostnamelen = 255;
              char *hn = new char[hostnamelen];

              if ( gethostname( hn, hostnamelen ) == -1 )
              {
                int error_number = errno;
                Smb4KError::error( ERROR_GETTING_HOSTNAME, TQString(), strerror( error_number ) );

                emit failed();
                emit finished();

                removeLockFile();

                return ok; // false
              }

              TQString hostname( hn );

              delete [] hn;

              if ( contents.grep( "# Entries for Smb4K users." ).count() == 0 )
              {
                contents.append( "# Entries for Smb4K users." );
                contents.append( "# Generated by Smb4K. Please do not modify!" );
                contents.append( "User_Alias\tSMB4KUSERS = "+TQString( "%1" ).arg( getpwuid( getuid() )->pw_name ) );
                contents.append( "Defaults:SMB4KUSERS\tenv_keep += \"PASSWD USER\"" );
                contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "+Smb4KSettings::smb4k_kill() );
                contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "+Smb4KSettings::smb4k_umount() );
                contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "+Smb4KSettings::smb4k_mount() );
                contents.append( "# End of Smb4K user entries." );

                write = true;
              }
              else
              {
                // Find the beginning and the end of the entries in
                // the sudoers file:
                TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
                TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

                for ( TQStringList::Iterator it = begin; it != end; ++it )
                {
                  if ( (*it).startsWith( "User_Alias\tSMB4KUSERS" ) && (*it).contains( getpwuid( getuid() )->pw_name, true ) == 0 )
                  {
                    (*it).append( ","+TQString( getpwuid( getuid() )->pw_name ) );

                    write = true;

                    break;
                  }
                  else
                  {
                    continue;
                  }
                }
              }

              break;
            }
            case Remove:
            {
              // Find the beginning and the end of the entries in
              // the sudoers file:
              TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
              TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

              // Now, check if the user is in the list of users. If he is,
              // remove him from there or remove all if he is the only one:
              for ( TQStringList::Iterator it = begin; it != end; ++it )
              {
                if ( (*it).startsWith( "User_Alias\tSMB4KUSERS" ) )
                {
                  TQString users = (*it).section( "=", 1, 1 ).stripWhiteSpace();

                  if ( users.contains( "," ) == 0 )
                  {
                    // In this case, there is only one user in the list. Check if
                    // it is the user who requested the removal:
                    if ( TQString::compare( users, getpwuid( getuid() )->pw_name ) == 0 )
                    {
                      // They are equal. Remove everything:
                      contents.erase( begin, end );
                      contents.remove( end );

                      write = true;

                      break;
                    }
                    else
                    {
                      // They are not equal: Do nothing.
                      break;
                    }
                  }
                  else
                  {
                    // In this case there is more than one user in the list.
                    // Remove the user who requested the removal:
                    TQStringList list = TQStringList::split( ",", users, false );
                    list.remove( getpwuid( getuid() )->pw_name );

                    (*it).replace( users, list.join( "," ) );

                    write = true;

                    break;
                  }
                }
                else
                {
                  continue;
                }
              }

              break;
            }
            default:
            {
              file.close();

              emit failed();
              emit finished();

              removeLockFile();

              return ok; // false
            }
          };

          if ( write )
          {
            // Prepare the contents: remove empty lines from the end.
            TQStringList::Iterator it = contents.end();

            // Move the iterator to the last entry in the list:
            --it;

            while ( (*it).stripWhiteSpace().isEmpty() )
            {
              it = contents.remove( it );
              --it;
            }

            // Now write the contents to the file. The permissions
            // will be preserved by this action.
            ts << contents.join( "\n" ) << endl;

            file.close();
          }
          else
          {
            // The entries are already in the file.
          }

          ok = true;

          emit finished();

          removeLockFile();

          return ok;
        }
        else
        {
          Smb4KError::error( ERROR_OPENING_FILE, canonical_path, file.errorString() );

          emit failed();
          emit finished();

          removeLockFile();

          return ok; // false
        }
      }
      else
      {
        // The user does not have enough access rights to perform
        // the modification of the sudoers file. So, we need to use
        // tdesu to get the contents of the file.

        // Compose the command:
        TQString command;
        command.append( "tdesu -t -c \"smb4k_cat " );
        command.append( canonical_path+"\"" );
        command.append( " ; sleep 2" );

        m_state = ReadSudoers;

        ok = true;

        *m_proc << command;
        m_proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );

        // The process is not finished, so finished() will be emitted
        // later and the lock file will also be removed at the end.

        return ok;
      }
    }
    else
    {
      Smb4KError::error( ERROR_FILE_NOT_FOUND, canonical_path );

      emit failed();
      emit finished();

      removeLockFile();

      return ok; // false
    }
  }
  else
  {
    // The error message has already been shown by
    // Smb4KFileIO::createLockFile()
    emit failed();
    emit finished();

    // We need not remove the lock file here.

    return ok; // false
  }

  return ok;
}


bool Smb4KFileIO::writeSuperTab( Smb4KFileIO::Operation operation )
{
  m_operation = operation;
  bool ok = false;

  // Stop here if nothing has changed:
  if ( m_operation == NoOperation )
  {
    emit finished();

    ok = true;

    return ok;
  }

  TQString file = "super.tab";

  if ( createLockFile( file ) )
  {
    // Find the file first:
    TQCString canonical_path = findFile( file );

    if ( !canonical_path.isEmpty() )
    {
      // Stat the file, so that we know that it is safe to
      // read from and write to it and whether we need to
      // ask for the super user's password:
      struct stat buf;

      if ( lstat( canonical_path, &buf ) == -1 )
      {
        int error_number = errno;

        Smb4KError::error( ERROR_GETTING_PERMISSIONS, canonical_path, strerror( error_number ) );

        emit failed();
        emit finished();

        removeLockFile();

        return ok; // false
      }

      // Look for the groups the user is in:
      long ngroups_max;
      ngroups_max = sysconf(_SC_NGROUPS_MAX);

      gid_t list[ngroups_max];

      if ( getgroups( ngroups_max, list ) == -1 )
      {
        int error_number = errno;

        Smb4KError::error( ERROR_GETTING_GIDS, TQString(), strerror( error_number ) );

        emit failed();
        emit finished();

        return ok; // false
      }

      gid_t sup_gid = 65534; // set this to gid 'nobody' for initialization
      bool found_gid = false;
      int i = 0;

      while ( list[i] )
      {
        if ( list[i] == buf.st_gid )
        {
          sup_gid = list[i];
          found_gid = true;
        }

        i++;
      }

      // Error out if the file is irregular.
      // Yes, yes, I know that this is normally done in a different
      // way and that there might be a race here, but, hey, right here
      // I don't care!
      if ( !S_ISREG( buf.st_mode ) || S_ISFIFO( buf.st_mode ) || S_ISLNK( buf.st_mode ) )
      {
        Smb4KError::error( ERROR_FILE_IS_IRREGULAR, canonical_path, TQString() );

        emit failed();
        emit finished();

        removeLockFile();

        return ok; // false
      }

      // Check access rights:
      if ( (buf.st_uid == getuid() && (buf.st_mode & 00600) == (S_IWUSR | S_IRUSR)) /* user */ ||
           (found_gid && buf.st_gid == sup_gid && (buf.st_mode & 00060) == (S_IWGRP | S_IRGRP)) /* group */ ||
           ((buf.st_mode & 00006) == (S_IWOTH | S_IROTH)) /* others */ )
      {
        // The user has read and write access.

        TQFile file ( canonical_path );
        TQStringList contents;

        if ( file.open( IO_ReadWrite ) )
        {
          TQTextStream ts( &file );
          ts.setEncoding( TQTextStream::Locale );

          contents = TQStringList::split( "\n", ts.read(), true );

          bool write = false;

          switch ( m_operation )
          {
            case Insert:
            {
              size_t hostnamelen = 255;
              char *hn = new char[hostnamelen];

              if ( gethostname( hn, hostnamelen ) == -1 )
              {
                int error_number = errno;
                Smb4KError::error( ERROR_GETTING_HOSTNAME, TQString(), strerror( error_number ) );

                emit failed();
                emit finished();

                removeLockFile();

                return ok; // false
              }

              TQString hostname( hn );

              delete [] hn;

              if ( contents.grep( "# Entries for Smb4K users." ).count() == 0 )
              {
                contents.append( "# Entries for Smb4K users." );
                contents.append( "# Generated by Smb4K. Please do not modify!" );
                contents.append( ":define Smb4KUsers "+TQString( "%1" ).arg( getpwuid( getuid() )->pw_name ) );
#ifndef __FreeBSD__
                contents.append( "smb4k_kill\t"+Smb4KSettings::smb4k_kill()+
                                  "\t$(Smb4KUsers)\tuid=root\tgid=root" );
                contents.append( "smb4k_umount\t"+Smb4KSettings::smb4k_umount()+
                                "\t$(Smb4KUsers)\tuid=root\tgid=root" );
                contents.append( "smb4k_mount\t"+Smb4KSettings::smb4k_mount()+
                                "\t$(Smb4KUsers)\tuid=root\tgid=root\tenv=PASSWD,USER" );
#else
                contents.append( "smb4k_kill\t"+Smb4KSettings::smb4k_kill()+
                                "\t$(Smb4KUsers)\tuid=root\tgid=wheel" );
                contents.append( "smb4k_umount\t"+Smb4KSettings::smb4k_umount()+
                                "\t$(Smb4KUsers)\tuid=root\tgid=wheel" );
                contents.append( "smb4k_mount\t"+Smb4KSettings::smb4k_mount()+
                                "\t$(Smb4KUsers)\tuid=root\tgid=wheel\tsetenv=HOME=$CALLER_HOME\tenv=PASSWD,USER" );
#endif
                contents.append( "# End of Smb4K user entries." );

                write = true;
              }
              else
              {
                // Find the beginning and the end of the entries in
                // the super.tab file:
                TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
                TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

                for ( TQStringList::Iterator it = begin; it != end; ++it )
                {
                  if ( (*it).startsWith( ":define Smb4KUsers" ) && (*it).contains( getpwuid( getuid() )->pw_name, true ) == 0 )
                  {
                    (*it).append( ","+TQString( getpwuid( getuid() )->pw_name ) );

                    write = true;

                    break;
                  }
                  else
                  {
                    continue;
                  }
                }
              }

              break;
            }
            case Remove:
            {
              // Find the beginning and the end of the entries in
              // the super.tab file:
              TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
              TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

              // Now, check if the user is in the list of users. If he is,
              // remove him from there or remove all if he is the only one:
              for ( TQStringList::Iterator it = begin; it != end; ++it )
              {
                if ( (*it).startsWith( ":define Smb4KUsers" ) )
                {
                  TQString users = (*it).section( "Smb4KUsers", 1, 1 ).stripWhiteSpace();

                  if ( users.contains( "," ) == 0 )
                  {
                    // In this case, there is only one user in the list. Check if
                    // it is the user who requested the removal:
                    if ( TQString::compare( users, getpwuid( getuid() )->pw_name ) == 0 )
                    {
                      // They are equal. Remove everything:
                      contents.erase( begin, end );
                      contents.remove( end );

                      write = true;

                      break;
                    }
                    else
                    {
                      // They are not equal: Do nothing.
                      break;
                    }
                  }
                  else
                  {
                    // In this case there is more than one user in the list.
                    // Remove the user who requested the removal:
                    TQStringList list = TQStringList::split( ",", users, false );
                    list.remove( getpwuid( getuid() )->pw_name );

                    (*it).replace( users, list.join( "," ) );

                    write = true;

                    break;
                  }
                }
                else
                {
                  continue;
                }
              }

              break;
            }
            default:
            {
              file.close();

              emit failed();
              emit finished();

              removeLockFile();

              return ok; // false
            }
          };

          if ( write )
          {
            // Prepare the contents: remove empty lines from the end.
            TQStringList::Iterator it = contents.end();

            // Move the iterator to the last entry in the list:
            --it;

            while ( (*it).stripWhiteSpace().isEmpty() )
            {
              it = contents.remove( it );
              --it;
            }

            // Now write the contents to the file. The permissions
            // will be preserved by this action.
            ts << contents.join( "\n" ) << endl;

            file.close();
          }
          else
          {
            // The entries are already in the file.
          }

          ok = true;

          emit finished();

          removeLockFile();

          return ok;
        }
        else
        {
          Smb4KError::error( ERROR_OPENING_FILE, canonical_path, file.errorString() );

          emit failed();
          emit finished();

          removeLockFile();

          return ok; // false
        }
      }
      else
      {
        // The user does not have enough access rights to perform
        // the modification of the sudoers file. So, we need to use
        // tdesu to get the contents of the file.

        // Compose the command:
        TQString command;
        command.append( "tdesu -t -c \"smb4k_cat " );
        command.append( canonical_path+"\"" );
        command.append( " ; sleep 2" );

        m_state = ReadSuperTab;

        ok = true;

        *m_proc << command;
        m_proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );

        // The process is not finished, so finished() will be emitted
        // later and the lock file will also be removed at the end.

        return ok;
      }
    }
    else
    {
      Smb4KError::error( ERROR_FILE_NOT_FOUND, canonical_path );

      emit failed();
      emit finished();

      removeLockFile();

      return ok; // false
    }
  }
  else
  {
    // The error message has already been shown by
    // Smb4KFileIO::createLockFile()
    emit failed();
    emit finished();

    // We need not remove the lock file here.

    return ok; // false
  }

  return ok;
}


bool Smb4KFileIO::createLockFile( const TQString &filename )
{
  bool ok = false;

  // Determine the directory where to write the lock file. First, try
  // /var/lock and than /var/tmp. If that does not work either, fall
  // back to /tmp.
  if ( m_lock_file.isEmpty() )
  {
    TQValueList<TQCString> dirs;
    dirs << "/var/lock" << "/var/tmp" << "/tmp";

    struct stat buf;

    for ( TQValueList<TQCString>::ConstIterator it = dirs.begin(); it != dirs.end(); ++it )
    {
      // First check if the directory is available and writable
      if ( lstat( *it, &buf ) == -1 )
      {
        int error_number = errno;

        if ( error_number != EACCES && error_number != ENOENT )
        {
          Smb4KError::error( ERROR_GETTING_PERMISSIONS, *it, strerror( error_number ) );

          return ok; // false
        }
      }
      else
      {
        // Look for the groups the user is in:
        long ngroups_max;
        ngroups_max = sysconf(_SC_NGROUPS_MAX);

        gid_t list[ngroups_max];

        if ( getgroups( ngroups_max, list ) == -1 )
        {
          int error_number = errno;

          Smb4KError::error( ERROR_GETTING_GIDS, TQString(), strerror( error_number ) );

          return ok; // false
        }

        gid_t sup_gid = 65534; // set this to gid 'nobody' for initialization
        bool found_gid = false;
        int i = 0;

        while ( list[i] )
        {
          if ( list[i] == buf.st_gid )
          {
            sup_gid = list[i];
            found_gid = true;
          }

          i++;
        }

        // Check whether we are stat'ing a directory and that the
        // user has read/write permissions.
        if ( S_ISDIR( buf.st_mode ) /* is directory */ &&
            (buf.st_uid == getuid() && (buf.st_mode & 00600) == (S_IWUSR | S_IRUSR)) /* user */ ||
            (found_gid && buf.st_gid == sup_gid && (buf.st_mode & 00060) == (S_IWGRP | S_IRGRP)) /* group */ ||
            ((buf.st_mode & 00006) == (S_IWOTH | S_IROTH)) /* others */ )
        {
          m_lock_file = *it+"/smb4k.lock";

          break;
        }
        else
        {
          continue;
        }
      }
    }
  }

  int file_descriptor;

  // Create the lock file if necessary and open it:
  if ( (file_descriptor = open( m_lock_file, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH )) == -1 )
  {
    // Error out if the opening failed:
    int error_number = errno;

    Smb4KError::error( ERROR_OPENING_FILE, m_lock_file, strerror( error_number ) );

    return ok; // false
  }
  else
  {
    // Check what we actually opened:

    struct stat file_stat;

    if ( fstat( file_descriptor, &file_stat ) == -1 )
    {
      // Error out if we could not get the information about the file:
      int error_number = errno;

      // FIXME for >= 0.8.x: Change error code to ERROR_GETTING_STAT
      Smb4KError::error( ERROR_GETTING_PERMISSIONS, TQString(), strerror( error_number ) );

      return ok;
    }

    if ( !S_ISREG( file_stat.st_mode ) || S_ISFIFO( file_stat.st_mode ) || S_ISLNK( file_stat.st_mode ) )
    {
      // Close the file and error out, if we have opened an
      // "irregular" file (i.e. a symlink, a fifo, etc.).

      Smb4KError::error( ERROR_FILE_IS_IRREGULAR, m_lock_file );

      if ( close( file_descriptor ) == -1 )
      {
        int error_number = errno;

        Smb4KError::error( ERROR_CLOSING_FILE, m_lock_file, strerror( error_number ) );
      }

      return ok; // false
    }
    else
    {
      // Continue if the file is regular.

      char buffer[1000];
      ssize_t size;

      if ( (size = read( file_descriptor, buffer, 1000 )) == -1 )
      {
        int error_number = errno;

        Smb4KError::error( ERROR_READING_FILE, m_lock_file, strerror( error_number ) );

        return ok; // false
      }

      if ( size >= 1000 )
      {
        // FIXME for >= 0.8.x: Change error code to ERROR_BUFFER_EXCEEDED
        Smb4KError::error( ERROR_UNKNOWN, TQString(), i18n( "Buffer size exceeded" ) );

        return ok; // false
      }

      TQStringList contents = TQStringList::split( '\n', TQString::fromLocal8Bit( buffer, size ), false );
      TQString test_string = ":"+filename;
      TQString entry = contents.grep( test_string, true ).join( "\n" ).stripWhiteSpace();

      if ( !entry.isEmpty() )
      {
        Smb4KError::error( ERROR_LOCKED, entry );

        return ok; // false
      }
      else
      {
        contents << TQString( "%1:%2" ).arg( getpwuid( getuid() )->pw_name ).arg( filename );
        TQCString out = contents.join( "\n" ).local8Bit();

        if ( write( file_descriptor, out, out.length() ) == -1 )
        {
          int error_number = errno;

          Smb4KError::error( ERROR_WRITING_FILE, m_lock_file, strerror( error_number ) );

          return ok; // false
        }

        if ( close( file_descriptor ) == -1 )
        {
          int error_number = errno;

          Smb4KError::error( ERROR_CLOSING_FILE, m_lock_file, strerror( error_number ) );

          return ok; // false
        }

        ok = true;
      }
    }
  }

  return ok;
}


bool Smb4KFileIO::removeLockFile( const bool error_message )
{
  // We already have the name and location of the lock
  // file, so we do not need to define it here.

  int file_descriptor;
  bool ok = false;

  // Open the lock file:
  if ( (file_descriptor = open( m_lock_file, O_RDWR )) == -1 )
  {
    // Error out if the opening failed:
    int error_number = errno;

    if ( error_message && error_number != ENOENT )
    {
      Smb4KError::error( ERROR_OPENING_FILE, m_lock_file, strerror( error_number ) );
    }

    return ok; // false
  }
  else
  {
    // Check what we actually opened:

    struct stat file_stat;

    if ( fstat( file_descriptor, &file_stat ) == -1 )
    {
      // Error out if we could not get the information about the file:
      int error_number = errno;

      if ( error_message )
      {
        // FIXME for >= 0.8.x: Change error code to ERROR_GETTING_STAT
        Smb4KError::error( ERROR_GETTING_PERMISSIONS, TQString(), strerror( error_number ) );
      }

      return ok;
    }

    if ( !S_ISREG( file_stat.st_mode ) || S_ISFIFO( file_stat.st_mode ) || S_ISLNK( file_stat.st_mode ) )
    {
      // Close the file and error out, if we have opened an
      // "irregular" file (i.e. a symlink, a fifo, etc.).

      if ( error_message )
      {
        Smb4KError::error( ERROR_FILE_IS_IRREGULAR, m_lock_file );
      }

      if ( close( file_descriptor ) == -1 )
      {
        int error_number = errno;

        if ( error_message )
        {
          Smb4KError::error( ERROR_CLOSING_FILE, m_lock_file, strerror( error_number ) );
        }
      }

      return ok; // false
    }
    else
    {
      // Continue if the file is regular.

      char buffer[1000];
      ssize_t size;

      if ( (size = read( file_descriptor, buffer, 1000 )) == -1 )
      {
        int error_number = errno;

        if ( error_message )
        {
          Smb4KError::error( ERROR_READING_FILE, m_lock_file, strerror( error_number ) );
        }

        return ok; // false
      }

      if ( size >= 1000 )
      {
        if ( error_message )
        {
          // FIXME for >= 0.8.x: Change error code to ERROR_BUFFER_EXCEEDED
          Smb4KError::error( ERROR_UNKNOWN, TQString(), i18n( "Buffer size exceeded" ) );
        }

        return ok; // false
      }

      TQStringList contents = TQStringList::split( '\n', TQString::fromLocal8Bit( buffer, size ), false );

      // Prepare the contents of the file and write it to the disk.
      // It it should be empty, remove the lock file.
      for ( TQStringList::Iterator it = contents.begin(); it != contents.end(); it++ )
      {
        if ( (*it).startsWith( TQString( getpwuid( getuid() )->pw_name )+":" ) )
        {
          *it = TQString();

          continue;
        }
        else
        {
          continue;
        }
      }

      contents.remove( TQString() );

      if ( !contents.isEmpty() )
      {
        // Write the remaining contents to the lock file:

        TQCString out = contents.join( "\n" ).local8Bit();

        if ( write( file_descriptor, out, out.length() ) == -1 )
        {
          int error_number = errno;

          if ( error_message )
          {
            Smb4KError::error( ERROR_WRITING_FILE, m_lock_file, strerror( error_number ) );
          }

          return ok; // false
        }

        if ( close( file_descriptor ) == -1 )
        {
          int error_number = errno;

          if ( error_message )
          {
            Smb4KError::error( ERROR_CLOSING_FILE, m_lock_file, strerror( error_number ) );
          }

          return ok; // false
        }

        ok = true;
      }
      else
      {
        // Close and remove the lock file:

        if ( close( file_descriptor ) == -1 )
        {
          int error_number = errno;

          if ( error_message )
          {
            Smb4KError::error( ERROR_CLOSING_FILE, m_lock_file, strerror( error_number ) );
          }

          return ok; // false
        }

        if ( unlink( m_lock_file ) == -1 )
        {
          int error_number = errno;

          if ( error_message )
          {
            // FIXME for > 0.8.x: Replace error code with ERROR_REMOVING_FILE
            Smb4KError::error( ERROR_UNKNOWN, m_lock_file, strerror( error_number ) );
          }

          return ok; // false
        }

        ok = true;
      }
    }
  }

  return ok;
}


const TQCString Smb4KFileIO::findFile( const TQString &filename )
{
  TQStringList paths;
  paths << "/etc";
  paths << "/etc/samba";
  paths << "/usr/local/etc";
  paths << "/usr/local/etc/samba";

  TQString canonical_path = TQString();

  for ( TQStringList::ConstIterator it = paths.begin(); it != paths.end(); it++ )
  {
    TQDir::setCurrent( *it );

    if ( TQFile::exists( filename ) )
    {
      canonical_path = TQDir::current().canonicalPath()+"/"+filename;

      break;
    }
    else
    {
      continue;
    }
  }

  return canonical_path.local8Bit();
}


void Smb4KFileIO::processSudoers()
{
  // If the output buffer is empty, we stop, because
  // that most likely means, the user cancelled the
  // tdesu dialog.
  if ( m_buffer.stripWhiteSpace().isEmpty() )
  {
    emit failed();
    emit finished();

    removeLockFile();

    return;
  }

  TQStringList contents = TQStringList::split( "\n", m_buffer, true );
  bool write = false;

  switch ( m_operation )
  {
    case Insert:
    {
      size_t hostnamelen = 255;
      char *hn = new char[hostnamelen];

      if ( gethostname( hn, hostnamelen ) == -1 )
      {
        int error_number = errno;
        Smb4KError::error( ERROR_GETTING_HOSTNAME, TQString(), strerror( error_number ) );

        emit failed();
        emit finished();

        removeLockFile();
      }

      TQString hostname( hn );

      delete [] hn;

      if ( contents.grep( "# Entries for Smb4K users." ).count() == 0 )
      {
        contents.append( "# Entries for Smb4K users." );
        contents.append( "# Generated by Smb4K. Please do not modify!" );
        contents.append( "User_Alias\tSMB4KUSERS = "+TQString( "%1" ).arg( getpwuid( getuid() )->pw_name ) );
        contents.append( "Defaults:SMB4KUSERS\tenv_keep += \"PASSWD USER\"" );
        contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "+Smb4KSettings::smb4k_kill() );
        contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "+Smb4KSettings::smb4k_umount() );
        contents.append( "SMB4KUSERS\t"+hostname+" = NOPASSWD: "+Smb4KSettings::smb4k_mount() );
        contents.append( "# End of Smb4K user entries." );

        write = true;
      }
      else
      {
        // Find the beginning and the end of the entries in
        // the sudoers file:
        TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
        TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

        for ( TQStringList::Iterator it = begin; it != end; ++it )
        {
          if ( (*it).startsWith( "User_Alias\tSMB4KUSERS" ) && (*it).contains( getpwuid( getuid() )->pw_name, true ) == 0 )
          {
            (*it).append( ","+TQString( getpwuid( getuid() )->pw_name ) );

            write = true;

            break;
          }
          else
          {
            continue;
          }
        }
      }

      break;
    }
    case Remove:
    {
      // Find the beginning and the end of the entries in
      // the sudoers file:
      TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
      TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

      // Now, check if the user is in the list of users. If he is,
      // remove him from there or remove all if he is the only one:
      for ( TQStringList::Iterator it = begin; it != end; ++it )
      {
        if ( (*it).startsWith( "User_Alias\tSMB4KUSERS" ) )
        {
          TQString users = (*it).section( "=", 1, 1 ).stripWhiteSpace();

          if ( users.contains( "," ) == 0 )
          {
            // In this case, there is only one user in the list. Check if
            // it is the user who requested the removal:
            if ( TQString::compare( users, getpwuid( getuid() )->pw_name ) == 0 )
            {
              // They are equal. Remove everything:
              contents.erase( begin, end );
              contents.remove( end );

              write = true;

              break;
            }
            else
            {
              // They are not equal: Do nothing.
              break;
            }
          }
          else
          {
            // In this case there is more than one user in the list.
            // Remove the user who requested the removal:
            TQStringList list = TQStringList::split( ",", users, false );
            list.remove( getpwuid( getuid() )->pw_name );

            (*it).replace( users, list.join( "," ) );

            write = true;

            break;
          }
        }
        else
        {
          continue;
        }
      }

      break;
    }
    default:
    {
      emit failed();
      emit finished();

      removeLockFile();

      return;
    }
  }

  if ( write )
  {
    // Prepare the contents: remove empty lines from the end.
    TQStringList::Iterator it = contents.end();

    // Move the iterator to the last entry in the list:
    --it;

    while ( (*it).stripWhiteSpace().isEmpty() )
    {
      it = contents.remove( it );
      --it;
    }

    // Create a temporary file and write the data to it:
    TQCString template_string = tempDir().local8Bit()+"/XXXXXX";
    char tmp[template_string.length()+1];
    (void) tqstrncpy( tmp, template_string, template_string.length()+1 );

    TQFile temp_file;
    int file_descriptor;

    if ( (file_descriptor = mkstemp( tmp )) == -1 )
    {
      int err = errno;

      Smb4KError::error( ERROR_CREATING_TEMP_FILE, tmp, strerror( err ) );

      emit failed();
      emit finished();

      removeLockFile();

      return;
    }

    if ( temp_file.open( IO_WriteOnly, file_descriptor ) )
    {
      TQTextStream ts( &temp_file );
      ts.setEncoding( TQTextStream::Locale );

      ts << contents.join( "\n" ) << endl;

      temp_file.close();
    }
    else
    {
      Smb4KError::error( ERROR_WRITING_FILE, temp_file.name() );

      emit failed();
      emit finished();

      removeLockFile();

      return;
    }

    // Now move the file to the right location. Preserve the permissions
    // and the owner:
    TQString canonical_path = findFile( "sudoers" );
    struct stat file_stat;

    if ( stat( canonical_path.local8Bit(), &file_stat ) == -1 )
    {
      int error_number = errno;

      Smb4KError::error( ERROR_GETTING_PERMISSIONS, TQString(), strerror( error_number ) );

      emit failed();
      emit finished();

      removeLockFile();

      return;
    }

    TQString perms = TQString( "%1" ).arg( (int)file_stat.st_mode, 0, 8 );
    perms = perms.right( 4 );
    TQString owner = TQString( "%1" ).arg( (int)file_stat.st_uid );
    TQString group = TQString( "%1" ).arg( (int)file_stat.st_gid );
    TQString temp_file_name = TQString( tmp );

    // Assemble the command.
    TQString command;
    command.append( "tdesu -n -c \"smb4k_mv "+owner+":"+group+" "+perms+" " );
    command.append( temp_file_name+" " );
    command.append( canonical_path+"\" ; " );
    command.append( "rm -f "+temp_file_name );

    m_state = WriteSudoers;

    *m_proc << command;

    m_proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
  }
  else
  {
    // Everything OK.
    emit finished();

    removeLockFile();
  }
}


void Smb4KFileIO::processSuperTab()
{
  // If the output buffer is empty, we stop, because
  // that most likely means, the user cancelled the
  // tdesu dialog.
  if ( m_buffer.stripWhiteSpace().isEmpty() )
  {
    emit failed();
    emit finished();

    removeLockFile();

    return;
  }

  TQStringList contents = TQStringList::split( "\n", m_buffer, true );
  bool write = false;

  switch ( m_operation )
  {
    case Insert:
    {
      size_t hostnamelen = 255;
      char *hn = new char[hostnamelen];

      if ( gethostname( hn, hostnamelen ) == -1 )
      {
        int error_number = errno;
        Smb4KError::error( ERROR_GETTING_HOSTNAME, TQString(), strerror( error_number ) );

        emit failed();
        emit finished();

        removeLockFile();
      }

      TQString hostname( hn );

      delete [] hn;

      if ( contents.grep( "# Entries for Smb4K users." ).count() == 0 )
      {
        contents.append( "# Entries for Smb4K users." );
        contents.append( "# Generated by Smb4K. Please do not modify!" );
        contents.append( ":define Smb4KUsers "+TQString( "%1" ).arg( getpwuid( getuid() )->pw_name ) );
#ifndef __FreeBSD__
        contents.append( "smb4k_kill\t"+Smb4KSettings::smb4k_kill()+
                         "\t$(Smb4KUsers)\tuid=root\tgid=root" );
        contents.append( "smb4k_umount\t"+Smb4KSettings::smb4k_umount()+
                         "\t$(Smb4KUsers)\tuid=root\tgid=root" );
        contents.append( "smb4k_mount\t"+Smb4KSettings::smb4k_mount()+
                         "\t$(Smb4KUsers)\tuid=root\tgid=root\tenv=PASSWD,USER" );
#else
        contents.append( "smb4k_kill\t"+Smb4KSettings::smb4k_kill()+
                         "\t$(Smb4KUsers)\tuid=root\tgid=wheel" );
        contents.append( "smb4k_umount\t"+Smb4KSettings::smb4k_umount()+
                         "\t$(Smb4KUsers)\tuid=root\tgid=wheel" );
        contents.append( "smb4k_mount\t"+Smb4KSettings::smb4k_mount()+
                         "\t$(Smb4KUsers)\tuid=root\tgid=wheel\tsetenv=HOME=$CALLER_HOME\tenv=PASSWD,USER" );
#endif
        contents.append( "# End of Smb4K user entries." );

        write = true;
      }
      else
      {
        // Find the beginning and the end of the entries in
        // the super.tab file:
        TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
        TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

        for ( TQStringList::Iterator it = begin; it != end; ++it )
        {
          if ( (*it).startsWith( ":define Smb4KUsers" ) && (*it).contains( getpwuid( getuid() )->pw_name, true ) == 0 )
          {
            (*it).append( ","+TQString( getpwuid( getuid() )->pw_name ) );

            write = true;

            break;
          }
          else
          {
            continue;
          }
        }
      }

      break;
    }
    case Remove:
    {
      // Find the beginning and the end of the entries in
      // the super.tab file:
      TQStringList::Iterator begin = contents.find( "# Entries for Smb4K users." );
      TQStringList::Iterator end = contents.find( "# End of Smb4K user entries." );

      // Now, check if the user is in the list of users. If he is,
      // remove him from there or remove all if he is the only one:
      for ( TQStringList::Iterator it = begin; it != end; ++it )
      {
        if ( (*it).startsWith( ":define Smb4KUsers" ) )
        {
          TQString users = (*it).section( "Smb4KUsers", 1, 1 ).stripWhiteSpace();

          if ( users.contains( "," ) == 0 )
          {
            // In this case, there is only one user in the list. Check if
            // it is the user who requested the removal:
            if ( TQString::compare( users, getpwuid( getuid() )->pw_name ) == 0 )
            {
              // They are equal. Remove everything:
              contents.erase( begin, end );
              contents.remove( end );

              write = true;

              break;
            }
            else
            {
              // They are not equal: Do nothing.
              break;
            }
          }
          else
          {
            // In this case there is more than one user in the list.
            // Remove the user who requested the removal:
            TQStringList list = TQStringList::split( ",", users, false );
            list.remove( getpwuid( getuid() )->pw_name );

            (*it).replace( users, list.join( "," ) );

            write = true;

            break;
          }
        }
        else
        {
          continue;
        }
      }

      break;
    }
    default:
    {
      emit failed();
      emit finished();

      removeLockFile();

      return;
    }
  }

  if ( write )
  {
    // Prepare the contents: remove empty lines from the end.
    TQStringList::Iterator it = contents.end();

    // Move the iterator to the last entry in the list:
    --it;

    while ( (*it).stripWhiteSpace().isEmpty() )
    {
      it = contents.remove( it );
      --it;
    }

    // Create a temporary file and write the data to it:
    TQCString template_string = tempDir().local8Bit()+"/XXXXXX";
    char tmp[template_string.length()+1];
    (void) tqstrncpy( tmp, template_string, template_string.length()+1 );

    TQFile temp_file;
    int file_descriptor;

    if ( (file_descriptor = mkstemp( tmp )) == -1 )
    {
      int err = errno;

      Smb4KError::error( ERROR_CREATING_TEMP_FILE, tmp, strerror( err ) );

      emit failed();
      emit finished();

      removeLockFile();

      return;
    }

    if ( temp_file.open( IO_WriteOnly, file_descriptor ) )
    {
      TQTextStream ts( &temp_file );
      ts.setEncoding( TQTextStream::Locale );

      ts << contents.join( "\n" ) << endl;

      temp_file.close();
    }
    else
    {
      Smb4KError::error( ERROR_WRITING_FILE, temp_file.name() );

      emit failed();
      emit finished();

      removeLockFile();

      return;
    }

    // Now move the file to the right location. Preserve the permissions
    // and the owner:
    TQString canonical_path = findFile( "super.tab" );
    struct stat file_stat;

    if ( stat( canonical_path.local8Bit(), &file_stat ) == -1 )
    {
      int error_number = errno;

      Smb4KError::error( ERROR_GETTING_PERMISSIONS, TQString(), strerror( error_number ) );

      emit failed();
      emit finished();

      removeLockFile();

      return;
    }

    TQString perms = TQString( "%1" ).arg( (int)file_stat.st_mode, 0, 8 );
    perms = perms.right( 4 );
    TQString owner = TQString( "%1" ).arg( (int)file_stat.st_uid );
    TQString group = TQString( "%1" ).arg( (int)file_stat.st_gid );
    TQString temp_file_name = TQString( tmp );

    // Assemble the command.
    TQString command;
    command.append( "tdesu -n -c \"smb4k_mv "+owner+":"+group+" "+perms+" " );
    command.append( temp_file_name+" " );
    command.append( canonical_path+"\" ; " );
    command.append( "rm -f "+temp_file_name );

    m_state = WriteSuperTab;

    *m_proc << command;

    m_proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput );
  }
  else
  {
    // Everything OK.
    emit finished();

    removeLockFile();
  }
}

/////////////////////////////////////////////////////////////////////////////
// TQT_SLOT IMPLEMENTATIONS
/////////////////////////////////////////////////////////////////////////////

void Smb4KFileIO::slotShutdown()
{
  removeLockFile( false );
}


void Smb4KFileIO::slotReceivedStderr( TDEProcess *, char *buf, int len )
{
  TQString error_output = TQString::fromLocal8Bit( buf, len );

  if ( error_output.contains( "smb4k_mv" ) != 0 )
  {
    m_error_occurred = true;

    TQString canonical_path = findFile( (m_state == WriteSudoers ? "sudoers" : "super.tab") );

    Smb4KError::error( ERROR_WRITING_FILE, canonical_path, m_buffer );

    emit failed();
    emit finished();

    removeLockFile();
  }
  else if ( error_output.contains( "smb4k_cat" ) != 0 )
  {
    m_error_occurred = true;

    TQString canonical_path = findFile( (m_state == ReadSudoers ? "sudoers" : "super.tab") );

    Smb4KError::error( ERROR_READING_FILE, canonical_path, m_buffer );

    emit failed();
    emit finished();

    removeLockFile();
  }
}


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


void Smb4KFileIO::slotProcessExited( TDEProcess * )
{
  m_proc->clearArguments();

  if ( !m_error_occurred )
  {
    switch ( m_state )
    {
      case ReadSudoers:
      {
        processSudoers();

        break;
      }
      case WriteSudoers:
      {
        emit finished();

        removeLockFile();

        break;
      }
      case ReadSuperTab:
      {
        processSuperTab();

        break;
      }
      default:
      {
        emit finished();

        removeLockFile();

        break;
      }
    }
  }
  else
  {
    // Smb4KFileIO::slotReceivedStderr() has already done the
    // necessary things.
  }

  m_buffer = TQString();
  m_state = Idle;
  m_error_occurred = false;
}

#include "smb4tdefileio.moc"
