/***************************************************************************
 *   Copyright (C) 2004 by Judd Baileys                                    *
 *   jbaileys@bigpond.net.au                                               *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#define LOGPATH "/.kdsl/logfile"

#include <config.h>

#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <locale.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>

#ifdef _XPG4_2
#define __xnet_connect	connect
#endif

#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

#include <kmessagebox.h>
#include <kapplication.h>
#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <klocale.h>
#include "kdsl.h"
#include "docking.h"
#include "configuration.h"
#include <kglobal.h>
#include <kstandarddirs.h>
#include <qfile.h>
#include <dcopclient.h>
#include "caller.h"
#include "main.h"
#include <kprocess.h>
#include <qobject.h>
#include "requester.h"
#include <kdebug.h>

#include <X11/Xlib.h>

static const char *description = I18N_NOOP("Kdsl - a front-end to the pppd tools");

static const char *version = "0.5";

static KCmdLineOptions options[] =
{
   { "c <account_name>", I18N_NOOP("Connect using 'account_name'"), 0 },
   { "k", I18N_NOOP("Terminate an existing connection"), 0 },
   { "q", I18N_NOOP("Quit after end of connection"), 0 },
   { "r <rule_file>", I18N_NOOP("Check syntax of rule_file"), 0 },
   { "T", I18N_NOOP("Enable test-mode"), 0 },
   	{ 0, 0, 0 }
};
// This code section was taken kppp
kdsl* m_kdsl;
//DockWidget* m_dockwidget;
// for testing purposes
bool TESTING=0;

// initial effective user id before possible suid status is dropped
uid_t euid;
// helper process' pid
pid_t helperPid = -1;

QString local_ip_address;
QString remote_ip_address;
QString pidfile;

int main(int argc, char **argv)
{

  if(fcntl(0, F_GETFL) == -1)
    (void)open("/dev/null", O_RDONLY);

  if(fcntl(1, F_GETFL) == -1)
    (void)open("/dev/null", O_WRONLY);

  if(fcntl(2, F_GETFL) == -1)
    (void)open("/dev/null", O_WRONLY);

   // Don't insert anything above this line unless you really know what
  // you're doing. We're most likely running setuid root here,
  // until we drop this status a few lines below.
  int sockets[2];
  if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) != 0) {
    fprintf(stderr, "error creating socketpair !\n");
    return 1;
  }

  switch(helperPid = fork()) {
  case 0:
    // child process
    // make process leader of new group
    setsid();
    umask(0);
    close(sockets[0]);
    signal(SIGHUP, SIG_IGN);
    (void) new Opener(sockets[1]);
    // we should never get here
    _exit(1);

  case -1:
    perror("fork() failed");
    exit(1);
  }

  // parent process
  close(sockets[1]);

  // drop setuid status
  euid = geteuid();
  setgid(getgid());
  setuid(getuid());

  //
  // end of setuid-dropping block.
  //  
  
  // install exit handler that will kill the helper process
  atexit(myShutDown);

  // not needed anymore, just causes problems with broken setup
  //  if(getHomeDir() != 0)
  //      setenv("HOME", getHomeDir(), 1);

  (void) new Requester(sockets[0]);

	    KAboutData about("kdsl", I18N_NOOP("kdsl"), version, description,
                     KAboutData::License_GPL, "(C) 2004 Judd Baileys", 0, 0, "jbaileys@bigpond.net.au");
    about.addAuthor( "Judd Baileys", 0, "jbaileys@bigpond.net.au" );

    KCmdLineArgs::init(argc, argv, &about);
    KCmdLineArgs::addCmdLineOptions( options );

    KApplication app;

   setlocale(LC_NUMERIC ,"C");

    gpppdata.open();

  kdDebug("kdsl") << "helperPid: " << (int) helperPid << endl;

    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();

 bool terminate_connection = args->isSet("k");


  // make sure that nobody can read the password from the
  // config file
  QString configFile = KGlobal::dirs()->saveLocation("config")
    + QString(kapp->name()) + "rc";
  if(access(QFile::encodeName(configFile), F_OK) == 0)
    chmod(QFile::encodeName(configFile), S_IRUSR | S_IWUSR);

  // do we really need to generate an empty directory structure here ?
  KGlobal::dirs()->saveLocation("appdata", "Rules");

  int pid = create_pidfile();
  QString err_msg = i18n("kdsl can't create or read from\n%1.").arg(pidfile);

  if(pid < 0) {
    KMessageBox::error(0L, err_msg);
    return 1;
  }

  if (terminate_connection) {
    setgid(getgid());
    setuid(getuid());
    if (pid > 0)
      kill(pid, SIGINT);
    else
      remove_pidfile();
    return 0;
  }


  if (pid > 0) {
     if(gpppdata.storeAutostart())
     return 1;
  
    QString msg = i18n("kdsl has detected a %1 file.\n"
                       "Another instance of kdsl seems to be "
                       "running under process-ID %2.\n"
                       "Please click Exit, make sure that you are "
                       "not running another kdsl, delete the pid "
                       "file, and restart kdsl.\n"
                       "Alternatively, if you have determined that "
                       "there is no other kdsl running, please "
                       "click Continue to begin.")
                  .arg(pidfile).arg(pid);
    int button = KMessageBox::warningYesNo(0, msg, i18n("Error"),
                                      i18n("Exit"), i18n("Continue"));
    if (button == KMessageBox::Yes)            /* exit */
       return 1;

    remove_pidfile();
    pid = create_pidfile();
    if(pid) {
      KMessageBox::error(0L, err_msg);
      return 1;
    }
  }
//DockWidget Dockwidget;
//m_dockwidget = &Dockwidget;
  
  kdsl Kdsl;
  m_kdsl = &Kdsl;
//  m_DockWidget = &Dockwidget;

  
//	app.setMainWidget(&Dockwidget);
	app.setTopWidget(&Kdsl); 
//	m_kdsl->show();
  
//  kdsl *mainWin=0;
  // we really don't want to die accidentally, since that would leave the
  // modem connected. If you really really want to kill me you must send
  // me a SIGKILL.
  signal(SIGINT, sighandler);
//  signal(SIGCHLD, sighandler);
  signal(SIGUSR1, sighandler);
  signal(SIGTERM, SIG_IGN);


 //   if (app.isRestored())
 //   {
//        RESTORE(kdsl);	

//    }
//    else
//    {
        // no session.. just start up normally
//        KCmdLineArgs *args = KCmdLineArgs::parsedArgs();

        /// @todo do something with the command line args here

 //       mainWin = new kdsl();
//        app.setMainWidget( mainWin );
//	app.setTopWidget( mainWin );
//        m_kdsl->show();
        args->clear();


//    }

    // mainWin has WDestructiveClose flag by default, so it will delete itself.
    int ret = app.exec();
  remove_pidfile();

  return ret;
}

pid_t execute_command (const QString & cmd) {
  QCString command = QFile::encodeName(cmd);
  if (command.isEmpty() || command.length() > COMMAND_SIZE)
    return (pid_t) -1;

  pid_t id;
//exit(0);
//exit(0);
  kdDebug("kdsl") << "Executing command: " << command << endl;

  QApplication::flushX();
  if((id = fork()) == 0) {
    // don't bother dieppp()
    signal(SIGCHLD, SIG_IGN);

    // close file descriptors
    for (int fd = 3; fd < 20; fd++)
      close(fd);

    // drop privileges if running setuid root
    setgid(getgid());
    setuid(getuid());

    system(command);
    _exit(0);
  }

  return id;
}


// Create a file containing the current pid. Returns 0 on success,
// -1 on failure or the pid of an already running kdsl process.
pid_t create_pidfile() {
  int fd = -1;
  char pidstr[40]; // safe

  pidfile = KGlobal::dirs()->saveLocation("appdata") + "kdsl.pid";

  if(access(QFile::encodeName(pidfile), F_OK) == 0) {

    if((access(QFile::encodeName(pidfile), R_OK) < 0) ||
       (fd = open(QFile::encodeName(pidfile), O_RDONLY)) < 0)
      return -1;

    int sz = read(fd, &pidstr, 32);
    close (fd);
    if (sz < 0)
      return -1;
    pidstr[sz] = '\0';

    kdDebug("kdsl") << "Kdsl appears to already be running\nfound kdsl.pid containing: " << pidstr << endl;

    // non-empty file ?
    if (sz > 0) {
      int oldpid;
      int match = sscanf(pidstr, "%d", &oldpid);

      // found a pid in pidfile ?
      if (match < 1 || oldpid <= 0)
        return -1;

      // check if process exists
      if (kill((pid_t)oldpid, 0) == 0 || errno != ESRCH)
        return oldpid;
    }

    kdDebug("kdsl") << "pidfile is stale\n" << endl;
    remove_pidfile();
  }

  if((fd = open(QFile::encodeName(pidfile), O_WRONLY | O_CREAT | O_EXCL,
                S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
    return -1;

  fchown(fd, getuid(), getgid());

  sprintf(pidstr, "%d\n", getpid());
  write(fd, pidstr, strlen(pidstr));
  close(fd);

  return 0;
}


bool remove_pidfile() {
  struct stat st;

  // only remove regular files with user write permissions
  if(stat(QFile::encodeName(pidfile), &st) == 0 )
    if(S_ISREG(st.st_mode) && (access(QFile::encodeName(pidfile), W_OK) == 0)) {
      unlink(QFile::encodeName(pidfile));
      return true;
    }

  fprintf(stderr, "error removing pidfile.\n");
  return false;
}


void myShutDown() {
  pid_t pid;
  // don't bother about SIGCHLDs anymore
  signal(SIGCHLD, SIG_IGN);
//   fprintf(stderr, "myShutDown(%i)\n", status);
  pid = helperPid;
  if(pid > 0) {
    helperPid = -1;
//        fprintf(stderr, "killing child process %i", pid);
    kill(pid, SIGKILL);
  }
}

 void sighandler(int sig) {
  QEvent *e = 0L;
  if(sig == SIGCHLD) {
   pid_t id = wait(0L);
    if(id >= 0 && id == helperPid) // helper process died
      e = new SignalEvent(sig);
  } else if(sig == SIGINT || sig == SIGUSR1)
    e = new SignalEvent(sig);

//   let eventFilter() deal with this when we're back in the loop
  if (e)
    QApplication::postEvent(m_kdsl, e);

  signal(sig, sighandler); // reinstall signal handler
} 


