/*
 *            kPPP: A pppd front end for the KDE project
 *
 * $Id: runtests.cpp 704673 2007-08-25 18:53:49Z amth $
 *
 *            Copyright (C) 1997 Bernd Johannes Wuebben
 *                   wuebben@math.cornell.edu
 *
 * This file was contributed by Mario Weilguni <mweilguni@sime.com>
 * Thanks Mario !
 *
 * 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 <qdir.h>
#include "runtests.h"
#include <ctype.h>
#include <unistd.h>
#include <kmessagebox.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <netinet/in.h>

#ifdef HAVE_RESOLV_H
#include <arpa/nameser.h>
#include <resolv.h>
#endif

#ifndef _PATH_RESCONF
#define _PATH_RESCONF "/etc/resolv.conf"
#endif

#include <klocale.h>
#include "pppdata.h"

// initial effective uid (main.cpp)
extern uid_t euid;

// secure pppd location (opener.cpp)
extern const char* pppdPath();

// shamelessly stolen from pppd-2.3.5
/********************************************************************
 *
 * Internal routine to decode the version.modification.patch level
 */

static void decode_version (const char *_buf, int *version,
			    int *modification, int *patch)
  {
    char *buffer = qstrdup(_buf);
    char *buf = buffer;
    *version      = (int) strtoul (buf, &buf, 10);
    *modification = 0;
    *patch        = 0;

    if (*buf == '.')
      {
	++buf;
	*modification = (int) strtoul (buf, &buf, 10);
	if (*buf == '.')
	  {
	    ++buf;
	    *patch = (int) strtoul (buf, &buf, 10);
	  }
      }

    if (*buf != '\0')
      {
	*version      =
	*modification =
	*patch        = 0;
      }

    delete [] buffer;
  }


void pppdVersion(int *version, int *modification, int *patch) {
  char buffer[30];
  const char *pppd;
  char *query;

  *version = *modification = *patch = 0;

  // locate pppd
  if(!(pppd = pppdPath()))
    return;

  // call pppd with --version option
  if(!(query = new char[strlen(pppd)+25]))
    return;
  strcpy(query, pppd);
  // had to add a dummy device to prevent a "no device specified
  // and stdin is not a tty" error from newer pppd versions.
  strcat(query, " --version /dev/tty 2>&1");
  fflush(0L);
  FILE *output = popen(query, "r");
  delete [] query;
  if(!output)
    return;

  // read output
  int size = fread(buffer, sizeof(char), 29, output);

  if(ferror(output)) {
    pclose(output);
    return;
  }
  pclose(output);
  buffer[size] = '\0';

  // find position of version number x.y.z
  char *p = buffer;
  while(*p && !isdigit(*p))
    p++;
  if (*p == 0)
    return;
  char *p2 = p;
  while(*p2 == '.' || isdigit(*p2))
    p2++;
  *p2 = '\0';

  decode_version(p, version, modification, patch);
}


int uidFromName(const char *uname) {
  struct passwd *pw;

  setpwent();
  while((pw = getpwent()) != NULL) {
    if(strcmp(uname, pw->pw_name) == 0) {
      int uid = pw->pw_uid;
      endpwent();
      return uid;
    }
  }

  endpwent();
  return -1;
}


const char *homedirFromUid(uid_t uid) {
  struct passwd *pw;
  char *d = 0;

  setpwent();
  while((pw = getpwent()) != NULL) {
    if(pw->pw_uid == uid) {
      d = strdup(pw->pw_dir);
      endpwent();
      return d;
    }
  }

  endpwent();
  return d;
}


const char* getHomeDir() {
  static const char *hd = 0;
  static bool ranTest = false;
  if(!ranTest) {
    hd = homedirFromUid(getuid());
    ranTest = true;
  }

  return hd;
}


int runTests() {
  int warning = 0;

  // Test pre-1: check if the user is allowed to dial-out
  if(access("/etc/kppp.allow", R_OK) == 0 && getuid() != 0) {
    bool access = false;
    FILE *f;
    if((f = fopen("/etc/kppp.allow", "r")) != NULL) {
      char buf[2048]; // safe
      while(f != NULL && !feof(f)) {
	if(fgets(buf, sizeof(buf), f) != NULL) {
	  QString s(buf);

	  s = s.trimmed();
	  if(s[0] == '#' || s.length() == 0)
	    continue;

	  if((uid_t)uidFromName(QFile::encodeName(s)) == getuid()) {
	    access = true;
	    fclose(f);
	    f = NULL;
	  }
	}
      }
      if(f)
	fclose(f);
    }

    if(!access) {
      KMessageBox::error(0,
		 i18n("You are not allowed to dial out with "
		      "kppp.\nContact your system administrator."));
      return TEST_CRITICAL;
    }
  }

  // Test 1: search the pppd binary
  const char *f = pppdPath();

  if(!f) {
    KMessageBox::error(0,
		 i18n("Cannot find the PPP daemon.\n"
                      "Make sure that pppd is installed."));
    warning++;
  }

  // Test 2: check access to the pppd binary
  if(f) {
#if 0
    if(access(f, X_OK) != 0 /* && geteuid() != 0 */) {
      KMessageBox::error(0,
		   i18n("You do not have the permission "
			"to start pppd.\n"
			"Contact your system administrator "
			"and ask to get access to pppd."));
      return TEST_CRITICAL;
    }
#endif

    if(euid != 0) {
      struct stat st;
      stat(f, &st);
      if(st.st_uid != 0 || (st.st_mode & S_ISUID) == 0) {
	KMessageBox::error(0,
                     i18n("You do not have sufficient permission to run\n"
                          "%1\n."
                          "Please make sure that kppp is owned by root "
                          "and has the SUID bit set.", f));
        warning++;
      }
    }
  }

  // Test 5: check for existence of /etc/resolv.conf
  if (access(_PATH_RESCONF, R_OK) != 0) {
    QString file = _PATH_RESCONF" ";
    QString msgstr = i18n("%1 is missing or can not be read.\n"
                   "Ask your system administrator to create "
                   "this file (can be empty) with appropriate "
                   "read and write permissions.", file);
    KMessageBox::error(0, msgstr);
    warning ++;
  }

  if(warning == 0)
    return TEST_OK;
  else
    return TEST_WARNING;
}

