/***************************************************************************
                          tdesudo.cpp  -  description
                             -------------------
    begin                : Sam Feb 15 15:42:12 CET 2003
    copyright            : (C) 2003 by Robert Gruber <rgruber@users.sourceforge.net>
                           (C) 2007 by Martin Böhm <martin.bohm@kubuntu.org>
                                       Anthony Mercatante <tonio@kubuntu.org>
                                       Canonical Ltd (Jonathan Riddell <jriddell@ubuntu.com>)
                           (C) 2011 by Timothy Pearson <kb9vqf@pearsoncomputing.net>

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "tdesudo.h"

#include <tqfile.h>
#include <tqdir.h>
#include <tqdatastream.h>
#include <tqstring.h>

#include <tdecmdlineargs.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kpushbutton.h>
#include <kpassdlg.h>
#include <kstandarddirs.h>
#include <tdesu/kcookie.h>
#include <kdebug.h>
#include <kshell.h>
#include <tdetempfile.h>

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>

TdeSudo::TdeSudo(TQWidget *parent, const char *name,const TQString& icon, const TQString& generic, bool withIgnoreButton)
	: KPasswordDialog(KPasswordDialog::Password, false, false, icon, parent, name)
{
	TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();

	TQString defaultComment = i18n("<b>%1</b> needs administrative privileges. Please enter your password for verification.");
	p=NULL;
	bError=false;

	m_pCookie = new KCookie;

	// Set vars
	bool newDcop = args->isSet("newdcop");
	bool realtime = args->isSet("r");
	bool priority = args->isSet("p");
	bool showCommand = (!args->isSet("d"));
	bool keepCurrentHome = true;
	bool changeUID = true;
	bool noExec = false;
	keepPwd = (!args->isSet("n"));
	emptyPwd = args->isSet("s");
	TQString runas = args->getOption("u");
	TQString cmd;

	if (!(args->isSet("c") && !args->getOption("c").stripWhiteSpace().isEmpty()) &&
	    !(!args->isSet("c") && args->count()) &&
	    !args->isSet("s"))
	{
		KMessageBox::information(NULL, i18n("No command arguments supplied!\nUsage: tdesudo [-u <runas>] <command>\nTdeSudo will now exit..."));
		noExec = true;
	}

	p = new TDEProcess;
	p->clearArguments();

	// Parsins args

	/* Get the comment out of cli args */
	TQByteArray commentBytes = args->getOption("comment");
	TQTextCodec* tCodecConv = TQTextCodec::codecForLocale();
	TQString comment = tCodecConv->toUnicode(commentBytes, commentBytes.size());

	if (args->isSet("f"))
	{
		// If file is writeable, do not change uid
		TQString filename = TQFile::decodeName(args->getOption("f"));
		TQString file = filename;
		if (!file.isEmpty())
		{
			if (file.at(0) != '/')
			{
				TDEStandardDirs dirs;
				dirs.addKDEDefaults();
				file = dirs.findResource("config", file);
				if (file.isEmpty())
				{
					kdError(1206) << "Config file not found: " << file << "\n";
					exit(1);
				}
			}
			TQFileInfo fi(file);
			if (!fi.exists())
			{
				kdError(1206) << "File does not exist: " << file << "\n";
				exit(1);
			}
			if (fi.isWritable())
			{
				changeUID = false;
			}
		}
	}

	if (withIgnoreButton)
	{
		setButtonText(User1, i18n("&Ignore"));
	}

	// Apologies for the C code, taken from tdelibs/tdesu/tdesu_stub.c
	// KControl and other places need to use the user's existing DCOP server
	// For that we set DCOPSERVER.  Create a file in /tmp and use iceauth to add magic cookies
	// from the existing server and set ICEAUTHORITY to point to the file
	if (!newDcop) {
		dcopServer = m_pCookie->dcopServer();
		TQCString dcopAuth = m_pCookie->dcopAuth();
		TQCString iceAuth = m_pCookie->iceAuth();

		FILE *fout;
		char iceauthority[200];
		char *host, *auth;
		host = tqstrdup(dcopServer.ascii());
		auth = tqstrdup(iceAuth);
		int tempfile;
		int oldumask = umask(077);

		strcpy(iceauthority, "/tmp/iceauth.XXXXXXXXXX");
		tempfile = mkstemp(iceauthority);
		umask(oldumask);
		if (tempfile == -1) {
			kdError() << "error in tdesudo mkstemp" << endl;
			exit(1);
		} else {
			// close(tempfile); //FIXME why does this make the connect() call later crash?
		}
		iceauthorityFile = iceauthority;
		//FIXME we should change owner of iceauthority file, but don't have permissions
		setenv("ICEAUTHORITY", iceauthorityFile.local8Bit(), 1);

		fout = popen("iceauth >/dev/null 2>&1", "w");
		if (!fout) {
			kdError() << "error in tdesudo running iceauth" << endl;
			exit(1);
		}
		fprintf(fout, "add ICE \"\" %s %s\n", host, auth);
		auth = tqstrdup(dcopAuth);
		//auth = xstrsep(params[P_DCOP_AUTH].value);
		fprintf(fout, "add DCOP \"\" %s %s\n", host, auth);
		unsetenv("ICEAUTHORITY");
		pclose(fout);

		// Exporting the user's sycoca
		kdeSycoca = TQFile::encodeName(locateLocal("cache", "tdesycoca"));
	}

	connect( p, TQ_SIGNAL(receivedStdout(TDEProcess*, char*, int)), this, TQ_SLOT(receivedOut(TDEProcess*, char*, int)) );
	connect( p, TQ_SIGNAL(receivedStderr(TDEProcess*, char*, int)), this, TQ_SLOT(receivedOut(TDEProcess*, char*, int)) );
	connect( p, TQ_SIGNAL(processExited (TDEProcess *)), this, TQ_SLOT(procExited(TDEProcess*)));

	TQString xauthenv = TQString(getenv("HOME")) + "/.Xauthority";
	p->setEnvironment("XAUTHORITY", xauthenv);

	// Generate the xauth cookie and put it in a tempfile
	// set the environment variables to reflect that.
	// Default cookie-timeout is 60 sec. .
	// 'man xauth' for more info on xauth cookies.
	
	KTempFile temp = KTempFile("/tmp/tdesudo-","-xauth");
	m_tmpname = temp.name();
	
	FILE *f;
	char buf[1024];
	
	TQCString disp = m_pCookie->display();
	// command: xauth -q -f m_tmpname generate $DISPLAy . trusted timeout 60
	TQString c = "/usr/bin/xauth -q -f " + m_tmpname + " generate " 
		+ TQString::fromLocal8Bit(disp) + " . trusted timeout 60";
	blockSigChild(); // pclose uses waitpid()

	if (!(f = popen(c.local8Bit(), "r"))) {
		kdWarning() << k_lineinfo << "Cannot run: " << c << "\n";
		unblockSigChild();
		return;
	}
	
	// non root users need to be able to read the xauth file.
	// the xauth file is deleted when tdesudo exits. security?
	TQFile tf(m_tmpname);
	if (!runas.isEmpty() && runas != "root" && tf.exists())
		chmod(m_tmpname.ascii(),0644);
	
	QCStringList output;
	while (fgets(buf, 1024, f) != NULL)
		output += buf; 
	if (pclose(f) < 0) {
		kdError() << k_lineinfo << "Could not run xauth.\n";
		unblockSigChild();
		return;
	}
	unblockSigChild();
	
	p->setEnvironment("DISPLAY", disp); 
	p->setEnvironment("XAUTHORITY", m_tmpname);

	if (emptyPwd)
		*p << "sudo" << "-k";
	else
	{
		if (changeUID)
		{
			*p << "sudo";

			if (!keepCurrentHome)
				*p << "-H";

			*p << "-S" << "-p" << "passprompt";

			if (!runas.isEmpty())
				*p << "-u" << runas;
		}

		if (!dcopServer.isEmpty())
			*p << "DCOPSERVER=" + dcopServer;

		if (!iceauthorityFile.isEmpty())
			*p << "ICEAUTHORITY=" + iceauthorityFile;

		if (!kdeSycoca.isEmpty())
			*p << "TDESYCOCA=" + kdeSycoca;

		if (realtime)
		{
			*p << "nice" << "-n" << "10";
			addLine(i18n("Priority:"), i18n("realtime:") + TQChar(' ') + TQString("50/100"));
		}
		else if (priority)
		{
			TQString n = args->getOption("p");
			int intn = atoi(n.ascii());
			intn =  (intn * 40 / 100) - (20 + 0.5);
		
			TQString strn;
			strn.sprintf("%d",intn);

			*p << "nice" << "-n" << strn;
			addLine(i18n("Priority:"), n + TQString("/100"));
		}

		*p << "--";

		if (args->isSet("c"))
		{
			TQString command = args->getOption("c");
			TQStringList commandSplit = KShell::splitArgs(command);
			
			for (int i = 0; i < commandSplit.count(); i++)
			{
				TQString arg = validArg(commandSplit[i]);
				*p << arg;
				if (i == 0)
					cmd += validArg(commandSplit[i]) + TQChar(' ');
				else
					cmd += TDEProcess::quote(validArg(commandSplit[i])) + TQChar(' ');
			}
		}

		if (args->count())
		{
			for (int i = 0; i < args->count(); i++)
			{
                                if ((!args->isSet("c")) && (i == 0))
				{
					TQStringList argsSplit = KShell::splitArgs(args->arg(i));
					for (int j = 0; j < argsSplit.count(); j++)
					{
						*p << validArg(argsSplit[j]);
						if (j == 0)
							cmd += validArg(argsSplit[j]) + TQChar(' ');
						else
							cmd += TDEProcess::quote(validArg(argsSplit[j])) + TQChar(' ');
					}
				}
				else
				{
					*p << validArg(args->arg(i));
					cmd += validArg(args->arg(i)) + TQChar(' ');
				}
			}
		}

		if (showCommand && !cmd.isEmpty())
			addLine(i18n("Command:"), cmd);
	}

	if (comment.isEmpty())
	{
		if (!generic.isEmpty())
			setPrompt(defaultComment.arg(generic));
		else
			setPrompt(defaultComment.arg(cmd));
	}
	else
		setPrompt(comment);

	if (noExec)
		exit(0);
	else
		p->start( TDEProcess::NotifyOnExit, TDEProcess::All );
}

TdeSudo::~TdeSudo()
{
}

void TdeSudo::receivedOut(TDEProcess*, char*buffer, int buflen)
{
	char *pcTmp= new char[buflen+1];
	strncpy(pcTmp,buffer,buflen);
	pcTmp[buflen]='\0';
	TQString strOut(pcTmp);

	std::cout << strOut.local8Bit() << std::endl;

	static int badpass = 0;

	if (strOut.find("Sorry, try again")!=-1)
	{
		badpass++;
		if (badpass>2)
		{
			bError=true;
			KMessageBox::error(this, i18n("Wrong password! Exiting..."));
			kapp->quit();
		}
	}
	if (strOut.find("command not found")!=-1)
	{
		bError=true;
		KMessageBox::error(this, i18n("Command not found!"));
		kapp->quit();
	}
	if (strOut.find("is not in the sudoers file")!=-1)
	{
		bError=true;
		KMessageBox::error(this, i18n("Your username is unknown to sudo!"));
                kapp->quit();
	}
	if (strOut.find("is not allowed to execute")!=-1)
	{
		bError=true;
		KMessageBox::error(this, i18n("Your user is not allowed to run the specified command!"));
		kapp->quit();
	}
	if (strOut.find("is not allowed to run sudo on")!=-1)
	{
		bError=true;
		KMessageBox::error(this, i18n("Your user is not allowed to run sudo on this host!"));
                kapp->quit();
	}
	if (strOut.find("may not run sudo on")!=-1)
	{
		bError=true;
		KMessageBox::error(this, i18n("Your user is not allowed to run sudo on this host!"));
                kapp->quit();
	}
	if ((strOut.find("passprompt")!=-1) || (strOut.find("PIN (CHV2)")!=-1))
	{
		this->clearPassword();
		this->show();
	}
}

void TdeSudo::procExited(TDEProcess*)
{
	if (!keepPwd && unCleaned)
	{
		unCleaned = false;
		p->clearArguments();
		*p << "sudo" << "-k";
		p->start( TDEProcess::NotifyOnExit, TDEProcess::All );
	}

	if (!newDcop && !iceauthorityFile.isEmpty())
		if (!iceauthorityFile.isEmpty())
		TQFile::remove(iceauthorityFile);

	if (!bError) {
		if (!m_tmpname.isEmpty())
			TQFile::remove(m_tmpname);
		kapp->quit();
	}
}

void TdeSudo::slotOk()
{
	TQString strTmp(password());
	strTmp+="\n";
	p->writeStdin(strTmp.ascii(),(int)strTmp.length());
	this->hide();
}

void TdeSudo::slotUser1()
{
	done(AsUser);
}

void TdeSudo::blockSigChild()
{
	sigset_t sset;
	sigemptyset(&sset);
	sigaddset(&sset, SIGCHLD);
	sigprocmask(SIG_BLOCK, &sset, 0L);
}
void TdeSudo::unblockSigChild()
{
	sigset_t sset;
	sigemptyset(&sset);
	sigaddset(&sset, SIGCHLD);
	sigprocmask(SIG_UNBLOCK, &sset, 0L);
}

TQString TdeSudo::validArg(TQString arg)
{
	TQChar firstChar = arg.at(0);
	TQChar lastChar = arg.at(arg.length() - 1);

	if ( (firstChar == '"' && lastChar == '"') || (firstChar == '\'' && lastChar == '\'') )
	{
		arg = arg.remove(0, 1);
		arg = arg.remove(arg.length() - 1, 1);
	}
	return arg;
}

#include "tdesudo.moc"
