/*
 *  Copyright (c) 1998 Denis Perchine <dyp@perchine.com>
 *  Copyright (c) 2004 Szombathelyi GyĂśrgy <gyurco@freemail.hu>
 *  Former maintainer: Adriaan de Groot <groot@kde.org>
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License version 2 as published by the Free Software Foundation.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#include "globals.h"

#include <ku_config.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/file.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include <fcntl.h>

#include <QDir>
#include <QSharedData>

#include <kstandarddirs.h>
#include <kmessagebox.h>
#include <kshell.h>
#include <kdebug.h>
#include <kio/netaccess.h>
#include <kurl.h>
#include <klocale.h>

#include "ku_user.h"
#include "ku_misc.h"

KU_User_Private::KU_User_Private()
{
  LastChange = 0;
  Expire = -1;
  UID     = 0;
  GID     = 100;

  Min     = 0;
  Max     = 99999;
  Warn    = 7;
  Inactive= -1;
//  s_flag    = 0;
  Caps = 0;
  CreateHome = false;
  CreateMailBox = false;
  CopySkel = false;
  DeleteHome = false;
  DeleteMailBox = false;

  Disabled = true;
}

// class KU_User

KU_User::KU_User()
{
  d = new KU_User_Private;
}

KU_User::KU_User(const KU_User *user)
{
  d = new KU_User_Private;
  copy(user);
}

void KU_User::copy(const KU_User *user)
{
  if ( user != this ) {
    *this = *user;
  }
}

bool KU_User::operator ==(const KU_User &other) const
{
  if ( getUID() == other.getUID() &&
       getName() == other.getName() )
    return true;
  else
    return false;
}

KU_User::~KU_User()
{
}

KU_PROPERTY_IMPL(KU_User,int,Caps)
KU_PROPERTY_IMPL(KU_User,QString,Name)
KU_PROPERTY_IMPL(KU_User,QString,Surname)
KU_PROPERTY_IMPL(KU_User,QString,Email)
KU_PROPERTY_IMPL(KU_User,QString,Pwd)
KU_PROPERTY_IMPL(KU_User,QString,HomeDir)
KU_PROPERTY_IMPL(KU_User,QString,Shell)
KU_PROPERTY_IMPL(KU_User,QString,FullName)
KU_PROPERTY_IMPL(KU_User,uid_t,UID)
KU_PROPERTY_IMPL(KU_User,uid_t,GID)
KU_PROPERTY_IMPL(KU_User,bool,Disabled)

//gecos
//--BSD gecos
KU_PROPERTY_IMPL(KU_User,QString,Office)
KU_PROPERTY_IMPL(KU_User,QString,WorkPhone)
KU_PROPERTY_IMPL(KU_User,QString,HomePhone)
KU_PROPERTY_IMPL(KU_User,QString,Class)
//--BSD end
KU_PROPERTY_IMPL(KU_User,QString,Office1)
KU_PROPERTY_IMPL(KU_User,QString,Office2)
KU_PROPERTY_IMPL(KU_User,QString,Address)

//shadow
KU_PROPERTY_IMPL(KU_User,QString,SPwd)
KU_PROPERTY_IMPL(KU_User,time_t,Expire)
KU_PROPERTY_IMPL(KU_User,time_t,LastChange)
KU_PROPERTY_IMPL(KU_User,int,Min)
KU_PROPERTY_IMPL(KU_User,int,Max)
KU_PROPERTY_IMPL(KU_User,int,Warn)
KU_PROPERTY_IMPL(KU_User,int,Inactive)
KU_PROPERTY_IMPL(KU_User,int,Flag)

//samba
KU_PROPERTY_IMPL(KU_User,QString, LMPwd)
KU_PROPERTY_IMPL(KU_User,QString, NTPwd)
KU_PROPERTY_IMPL(KU_User,QString, LoginScript)
KU_PROPERTY_IMPL(KU_User,QString, ProfilePath)
KU_PROPERTY_IMPL(KU_User,QString, HomeDrive)
KU_PROPERTY_IMPL(KU_User,QString, HomePath)
KU_PROPERTY_IMPL(KU_User,QString, Workstations)
KU_PROPERTY_IMPL(KU_User,QString, Domain)
KU_PROPERTY_IMPL(KU_User,SID, SID)
KU_PROPERTY_IMPL(KU_User,SID, PGSID)

//Administrative
KU_PROPERTY_IMPL(KU_User,bool, CreateHome)
KU_PROPERTY_IMPL(KU_User,bool, CreateMailBox)
KU_PROPERTY_IMPL(KU_User,bool, CopySkel)
KU_PROPERTY_IMPL(KU_User,bool, DeleteHome)
KU_PROPERTY_IMPL(KU_User,bool, DeleteMailBox)

int KU_User::createHome()
{

  if(d->HomeDir.isNull() || d->HomeDir.isEmpty()) {
    KMessageBox::sorry( 0, i18n("Cannot create home folder for %1: it is null or empty.", d->Name) );
    return(0);
  }
  if (mkdir(QFile::encodeName(d->HomeDir), 0700) != 0) {
    if (errno != EEXIST)
    {
      KMessageBox::error( 0, i18n("Cannot create home folder %1.\nError: %2", d->HomeDir, QString::fromLocal8Bit(strerror(errno))) );
      return(0);
    }
  }

  if (chown(QFile::encodeName(d->HomeDir), d->UID, d->GID) != 0) {
    KMessageBox::error( 0, i18n("Cannot change owner of home folder %1.\nError: %2", d->HomeDir, QString::fromLocal8Bit(strerror(errno))) );
    return(1);
  }

  if (chmod(QFile::encodeName(d->HomeDir), KU_HOMEDIR_PERM) != 0) {
    KMessageBox::error( 0, i18n("Cannot change permissions on home folder %1.\nError: %2", d->HomeDir, QString::fromLocal8Bit(strerror(errno))) );
    return(1);
  }
  return(1);
}

int KU_User::tryCreate(const QString &dir)
{
  struct stat sb;
  int rc = 0;

  rc = stat(QFile::encodeName(dir), &sb);
  if (rc == 0) {
    if (S_ISDIR(sb.st_mode)) {
      if (KMessageBox::warningContinueCancel( 0,
        i18n("Folder %1 already exists.\nWill make %2 owner and change permissions.\nDo you want to continue?", dir, d->Name),
        QString(), KStandardGuiItem::cont() ) == KMessageBox::Continue) {

        if (chown(QFile::encodeName(dir), d->UID, d->GID) != 0) {
          KMessageBox::error( 0, i18n("Cannot change owner of %1 folder.\nError: %2" , dir, QString::fromLocal8Bit(strerror(errno))) );
        }
        return(0);
      } else {
        KMessageBox::information( 0, i18n("Folder %1 left 'as is'.\nVerify ownership and permissions for user %2 who may not be able to log in.", dir, d->Name) );
        return(-1);
      }
    } else {
      KMessageBox::information( 0, i18n("%1 exists and is not a folder. User %2 will not be able to log in.", dir, d->Name) );
      return(-1);
    }
  } else {
    if (errno == ENOENT) {
      if (mkdir(QFile::encodeName(dir), 0700) != 0) {
        KMessageBox::error( 0, i18n("Cannot create %1 folder.\nError: %2", dir, QString::fromLocal8Bit(strerror(errno))));
        return(-1);
      }
      if (chown(QFile::encodeName(dir), d->UID, d->GID) != 0) {
        KMessageBox::error( 0, i18n("Cannot change owner of %1 folder.\nError: %2", dir, QString::fromLocal8Bit(strerror(errno))) );
      }
      return(0);
    } else {
      KMessageBox::error( 0, i18n("stat call on %1 failed.\nError: %2", dir, QString::fromLocal8Bit(strerror(errno))) );
      return(-1);
    }
  }
}

int KU_User::createMailBox()
{
  QString mailboxpath;
  int fd;
  mailboxpath = QFile::decodeName(MAIL_SPOOL_DIR) + QLatin1Char( '/' ) + d->Name;
  if((fd = open(QFile::encodeName(mailboxpath), O_CREAT|O_EXCL|O_WRONLY,
                S_IRUSR|S_IWUSR)) < 0) {
    if (errno != EEXIST)
    {
      KMessageBox::error( 0, i18n("Cannot create %1: %2",
                 mailboxpath,
                 QString::fromLocal8Bit(strerror(errno))) );
      return -1;
    }
  }

  close(fd);

  if (chown(QFile::encodeName(mailboxpath), d->UID, KU_MAILBOX_GID) != 0) {
    KMessageBox::error( 0, i18n("Cannot change owner on mailbox: %1\nError: %2",
                 mailboxpath, QString::fromLocal8Bit(strerror(errno))) );
    return -1;
  }

  if (chmod(QFile::encodeName(mailboxpath), KU_MAILBOX_PERM) != 0) {
    KMessageBox::error( 0, i18n("Cannot change permissions on mailbox: %1\nError: %2",
                 mailboxpath, QString::fromLocal8Bit(strerror(errno))) );
    return -1;
  }

  return 0;
}

int KU_User::copySkel()
{
  QDir source(QFile::decodeName(SKELDIR));
  QDir dest(d->HomeDir);
  mode_t mode;

  if (!source.exists()) {
    KMessageBox::error( 0, i18n("Folder %1 does not exist, cannot copy skeleton for %2.", source.absolutePath(), d->Name) );
    return (-1);
  }

  if (!dest.exists()) {
    KMessageBox::error( 0, i18n("Folder %1 does not exist, cannot copy skeleton.", dest.absolutePath()) );
    return (-1);
  }

  mode = umask(0007);
  copyDir(source.absolutePath(), dest.absolutePath(), d->UID, d->GID);
  umask( mode );

  return 0;
}

int KU_User::removeHome()
{
  struct stat sb;

  if (!stat(QFile::encodeName(d->HomeDir), &sb))
    if (S_ISDIR(sb.st_mode) && sb.st_uid == d->UID) {
      if (!KIO::NetAccess::del(KUrl(d->HomeDir),0L)) {
             KMessageBox::error( 0, i18n("Cannot remove home folder %1.\nError: %2",
                        d->HomeDir, KIO::NetAccess::lastErrorString()) );
      }
    } else {
      KMessageBox::error( 0, i18n("Removal of home folder %1 failed (uid = %2, gid = %3).", d->HomeDir, sb.st_uid, sb.st_gid) );
    }
  else {
    KMessageBox::error( 0, i18n("stat call on file %1 failed.\nError: %2",
                  d->HomeDir, QString::fromLocal8Bit(strerror(errno))) );
  }

  return 0;
}

//TODO: remove at jobs too.

int KU_User::removeCrontabs()
{
  QString file;
  QString command;

  file = QFile::decodeName(CRONTAB_DIR) + QLatin1Char( '/' ) + d->Name;
  if ( access(QFile::encodeName(file), F_OK) == 0 ) {
    command = QString::fromLatin1("crontab -u %1 -r").arg(KShell::quoteArg(d->Name));
    if ( system(QFile::encodeName(command)) != 0 ) {
      KMessageBox::error( 0, i18n("Cannot remove crontab %1.\nError: %2",
                   command, QString::fromLocal8Bit(strerror(errno))) );
     }
  }

  return 0;
}

int KU_User::removeMailBox()
{
  QString file;

  file = QFile::decodeName(MAIL_SPOOL_DIR) + QLatin1Char( '/' ) + d->Name;
  if (remove(QFile::encodeName(file)) != 0) {
    KMessageBox::error( 0, i18n("Cannot remove mailbox %1.\nError: %2",
                 file, QString::fromLocal8Bit(strerror(errno))) );
  }

  return 0;
}

KU_Users::KU_Users(KU_PrefsBase *cfg)
{
  mCfg = cfg;
}

KU_Users::~KU_Users()
{
}

const QString &KU_Users::getDOMSID() const
{
  return domsid;
}

void KU_Users::parseGecos( const char *gecos, QString &name,
  QString &field1, QString &field2, QString &field3 )
{
  int no = 0;
  const char *s = gecos;
  const char *pos = NULL;
  // At least one part of the string exists
  for(;;) {
    pos = strchr(s, ',');
    QString val;
    if(pos == NULL)
      val = QString::fromLocal8Bit(s);
    else
      val = QString::fromLocal8Bit(s, (int)(pos-s));

    switch(no) {
      case 0: name = val; break;
      case 1: field1 = val; break;
      case 2: field2 = val; break;
      case 3: field3 = val; break;
    }
    if(pos == NULL) break;
    s = pos+1;
    no++;
  }
}

void KU_Users::fillGecos(KU_User &user, const char *gecos)
{
  QString name,field1,field2,field3;
  parseGecos( gecos, name, field1, field2, field3 );
  user.setFullName( name );
  caps & Cap_BSD ? user.setOffice( field1 ) : user.setOffice1( field1 );
  caps & Cap_BSD ? user.setWorkPhone( field2 ) : user.setOffice2( field2 );
  caps & Cap_BSD ? user.setHomePhone( field3 ) : user.setAddress( field3 );
}

bool KU_Users::doCreate(KU_User *user)
{
  QString h_dir;

  if(user->getCreateMailBox()) {
    user->createMailBox();
    user->setCreateMailBox(false);
  }

  if(user->getCreateHome()) {
    if(user->createHome()) {
      user->setCreateHome(false);
     } else {
       return false; // if createHome fails, copySkel is irrelevant!
     }

     if(user->getCopySkel()) {
       if((user->copySkel()) == 0) {
         user->setCopySkel(false);
       }
     }

  }
  return true;
}

bool KU_Users::doDelete( KU_User *user )
{
  kDebug() << "delete user: " << user->getName() << " uid: " << user->getUID();
  if ( user->getDeleteHome() ) {
    user->removeHome();
    user->removeCrontabs();
  }
  if ( user->getDeleteMailBox() )
    user->removeMailBox();

  return true;
}

int KU_Users::lookup(const QString & name) const
{
  for ( int i = 0; i<count(); i++ ) {
    if ( at(i).getName() == name ) return i;
  }
  return -1;
}

int KU_Users::lookup(uid_t uid) const
{
  for ( int i = 0; i<count(); i++ ) {
    if ( at(i).getUID() == uid ) return i;
  }
  return -1;
}

int KU_Users::lookup_sam( const SID &sid ) const
{
  for ( int i = 0; i<count(); i++ ) {
    if ( at(i).getSID() == sid ) return i;
  }
  return -1;
}

int KU_Users::lookup_sam( const QString &sid ) const
{
  for ( int i = 0; i<count(); i++ ) {
    if ( at(i).getSID().getSID() == sid ) return i;
  }
  return -1;
}

int KU_Users::lookup_sam( uint rid ) const
{
  for ( int i = 0; i<count(); i++ ) {
    if ( at(i).getSID().getRID() == rid ) return i;
  }
  return -1;
}

uid_t KU_Users::first_free() const
{
  uid_t t;

  for (t = mCfg->firstUID() ; t<65534; t++)
    if (lookup(t) == -1)
      return t;

  return NO_FREE;
}

uint KU_Users::first_free_sam() const
{
  uint t;

  for (t = 1000; t<65534; t++)
    if (lookup_sam(t) == -1)
      return t;

  return 0;
}

void KU_Users::add(const KU_User &user)
{
  mAdd.append( user );
}

void KU_Users::del( int index)
{
  mDel.append( index );
}

void KU_Users::mod(int index, const KU_User &newuser)
{
  mMod.insert( index, newuser );
}

void KU_Users::commit()
{
  kDebug() << "KU_Users::commit()";

  for ( ModList::Iterator it = mModSucc.begin(); it != mModSucc.end(); ++it ) {
      replace(it.key(),*it);
  }
  for ( AddList::Iterator it = mAddSucc.begin(); it != mAddSucc.end(); ++it ) {
      append(*it);
  }
  for ( DelList::Iterator it = mDelSucc.begin(); it != mDelSucc.end(); ++it ) {
      removeAt(*it);
  }
  cancelMods();
}

void KU_Users::cancelMods()
{
  mAdd.clear();
  mDel.clear();
  mMod.clear();
  mAddSucc.clear();
  mDelSucc.clear();
  mModSucc.clear();
}
