/*
 *            kPPP: A pppd front end for the KDE project
 *
 *            Copyright (C) 1997 Bernd Johannes Wuebben
 *                   wuebben@math.cornell.edu
 *            Copyright (C) 1998-2002 Harri Porten <porten@kde.org>
 *
 * based on EzPPP:
 * Copyright (C) 1997  Jay Painter
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <config-kppp.h>

#include "main.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 <kaboutdata.h>
#include <kapplication.h>
#include <kcmdlineargs.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>

#include "kpppwidget.h"
#include "opener.h"
#include "pppdata.h"
#include "providerdb.h"
#include "version.h"
#include "requester.h"

//#include <X11/Xlib.h>
//Added by qt3to4:
#include <QEvent>

static const char description[] =
	I18N_NOOP("A dialer and front-end to pppd");


KPPPWidget*	p_kppp;

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

#if 0
extern "C" {
  static int kppp_x_errhandler( Display *dpy, XErrorEvent *err ) {
    char errstr[256]; // safe

    /*
      if(gpppdata.pppdpid() >= 0) {
      kill(gpppdata.pppdpid(), SIGTERM);
      }

      p_kppp->stopAccounting();
      removedns();
      unlockdevice();*/

    XGetErrorText( dpy, err->error_code, errstr, 256 );
    kFatal() << "X Error: " << errstr;
    kFatal() << "Major opcode: " << err->request_code;
    exit(256);
    return 0;
  }


  static int kppp_xio_errhandler( Display * ) {
    if(gpppdata.get_xserver_exit_disconnect()) {
      fprintf(stderr, "X11 Error!\n");
      if(gpppdata.pppdRunning())
        Requester::rq->killPPPDaemon();

      p_kppp->stopAccounting();
      removedns();
      Modem::modem->unlockdevice();
      return 0;
    } else{
      kFatal() << "Fatal IO error: client killed";
      exit(256);
      return 0;
    }
  }
} /* extern "C" */
#endif

int main( int argc, char **argv ) {
  // make sure that open/fopen and so on NEVER return 1 or 2 (stdout and stderr)
  // Expl: if stdout/stderr were closed on program start (by parent), open()
  // would return a FD of 1, 2 (or even 0 if stdin was closed too)
  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();
  if (setgid(getgid()) < 0 && errno != EPERM) {
    perror("setgid() failed");
    exit(1);
  }
  setuid(getuid());
  if (geteuid() != getuid()) {
    perror("setuid() failed");
    exit(1);
  }

  //
  // 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 aboutData("kppp", 0, ki18n("KPPP"),
    KPPPVERSION, ki18n(description), KAboutData::License_GPL,
    ki18n("(c) 1999-2002, The KPPP Developers"));
  aboutData.addAuthor(ki18n("Harri Porten"), ki18n("Current maintainer"), "porten@kde.org");
  aboutData.addAuthor(ki18n("Bernd Wuebben"), ki18n("Original author"), "wuebben@kde.org");
  aboutData.addAuthor(ki18n("Mario Weilguni"));

  KCmdLineArgs::init( argc, argv, &aboutData );

  KCmdLineOptions options;
  options.add("c <account_name>", ki18n("Connect using 'account_name'"));
  options.add("m <modem_name>", ki18n("Connect using 'modem_name'"));
  options.add("k", ki18n("Terminate an existing connection"));
  options.add("q", ki18n("Quit after end of connection"));
  options.add("r <rule_file>", ki18n("Check syntax of rule_file"));
  options.add("T", ki18n("Enable test-mode"));
  options.add("dev <device_name>", ki18n("Use the specified device"));
  KCmdLineArgs::addCmdLineOptions( options );



  KApplication a;

  // set portable locale for decimal point
  setlocale(LC_NUMERIC ,"C");

  // open configuration file
  gpppdata.open();

  kDebug(5002) << "helperPid: " << (int) helperPid;

  KCmdLineArgs *args = KCmdLineArgs::parsedArgs();

  bool terminate_connection = args->isSet("k");
  if (args->isSet("r"))
    return RuleSet::checkRuleFile(args->getOption("r"));

  TESTING = args->isSet("T");

  // make sure that nobody can read the password from the
  // config file
  QString configFile = KGlobal::dirs()->saveLocation("config")
    + kapp->objectName() + "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("kppp can not create or read from\n%1.", 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;
  }

  // Mario: testing
  if(TESTING) {
    gpppdata.open();
    gpppdata.setAccountByIndex(0);

    QString s = argv[2];
    urlEncode(s);
    kDebug(5002) << s;

    remove_pidfile();
    return 0;
  }

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

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

  KPPPWidget kppp;
  p_kppp = &kppp;

  (void)new DockWidget(p_kppp->con_win, "dockw", p_kppp->stats);

  // 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);

  //  XSetErrorHandler( kppp_x_errhandler );
  //  XSetIOErrorHandler( kppp_xio_errhandler );

  int ret = a.exec();

  remove_pidfile();

  return ret;
}


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

  pid_t id;

  kDebug(5002) << "Executing command: " << command;

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

    // close file descriptors
    const int open_max = sysconf( _SC_OPEN_MAX );
    for (int fd = 3; fd < open_max; ++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 kppp process.
pid_t create_pidfile() {
  int fd = -1;
  char pidstr[40]; // safe

  pidfile = KGlobal::dirs()->saveLocation("appdata") + "kppp.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';

    kDebug(5002) << "found kppp.pid containing: " << pidstr;

    // 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;
    }

    kDebug(5002) << "pidfile is stale\n";
    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(p_kppp, e);

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

