ultimatepp/bazaar/SysExec/SudoLib.cpp
micio 47abcc9486 Bazaar/SysExec : replacing back Buffer<> with malloc because of memory corruption on destructor
git-svn-id: svn://ultimatepp.org/upp/trunk@3087 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2011-01-25 09:02:52 +00:00

314 lines
8.1 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////////////////////
// a working (better ??) implementation of libsudo equivalent
// allows to run sudo either in sync or async mode
////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "SysExec.h"
#include "ArgEnv.h"
NAMESPACE_UPP
#ifdef PLATFORM_POSIX
#include <sys/wait.h>
#include <pty.h>
#include <utmp.h>
// a couple of stream helpers... we can't use Upp ones here because we need non-blocking streams
static void _PutLine(FILE *f, const char *line)
{
fprintf(f, "%s\n", line);
}
static String _GetLine(FILE *f)
{
static char buf[256];
*buf = 0;
fgets(buf, 255, f);
return buf;
}
///////////////////////////////////////////////////////////////////////////////////////////////
// executes a command via sudo; if wait is true, will wait for command end, otherwise executes it in background
bool SudoExec(String user, String const &password, String const &args, VectorMap<String, String> const &envir, bool w)
{
// root user as default
if(user == "")
user = "root";
// take this damn'd thing from environment
// it will make some apps crash... don't know why
VectorMap<String, String>env(envir, 1);
if(env.Find("DBUS_SESSION_BUS_ADDRESS"))
env.RemoveKey("DBUS_SESSION_BUS_ADDRESS");
// setup XAUTHORITY for new user, if different
// from current one -- this will allow using X display
// for spawned user
if(user != GetUserName())
{
String xFile = env.Get("XAUTHORITY", "/home/" + GetUserName() + "/.Xauthority");
if(FileExists(xFile))
{
// create a temp location for copied xFile
String tmpFile = GetTempFileName();
FileCopy(xFile, tmpFile);
env.RemoveKey("XAUTHORITY");
env.Add("XAUTHORITY", tmpFile);
}
}
// prepare args for sudo execution
String sudoArgs = "-H -E -p gimmipass ";
sudoArgs += "-u " + user;
sudoArgs += " -- " + args;
String clearArgs = "-K";
// prepare args buffers
char **argv = BuildArgs("sudo", sudoArgs);
char **clearArgv = BuildArgs("sudo", clearArgs);
// prepare environment buffer
char **envv = BuildEnv(env);
// forks the process to launch sudo command inside child
// providing a pseudoconsole for sudo.. it refuses to run without
// beware, forkpty() don't work here... probably the setsid()
// needs to happen before login_tty()
int result = 0;
int masterPty, slavePty;
if(openpty(&masterPty, &slavePty, NULL, NULL, NULL) == -1)
return false;
pid_t pid = fork();
if(pid < 0)
return false;
if(pid == 0)
{
// child process
// becomes the session leader
if(setsid() == -1)
_exit(-1);
// login into pseudoconsole
if(login_tty(slavePty) == -1)
_exit(-1);
// ignore tty hangups so we can close
// the pseudotty later
signal(SIGHUP, SIG_IGN);
// we shall first clear the password stored, if any
int cPid = fork();
if(cPid == -1)
_exit(-1);
else if(cPid == 0)
{
result = execvp("/usr/bin/sudo", clearArgv);
_exit(0);
}
else
{
int status;
wait(&status);
}
// executes sudo
result = execvpe("/usr/bin/sudo", argv, envv);
// just in case execvp failed
_exit(result);
}
else
{
// parent process
// associates an stream to child process pseudoterminal
FILE *sudoFile = fdopen(masterPty, "w+");
// set input pipe to non-blocking, so we can read as we like
// without hanging the program -- we need to check for 'password' input
fcntl(masterPty, F_SETFL, O_NONBLOCK);
// even if we can read from stream, we wait that there's somethink to read
// or, 1 second if nothing there, at most, to avoid that sudo prompt
// arrives too late to catch it
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(masterPty, &rfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
select(masterPty + 1, &rfds, NULL, NULL, &tv);
// now we can try to read the prompt; it could be the 'gimmipass'
// we choose, or some error message, garbage or even nothing
// we repeat read until we find OR the passprompt required, OR
// some error messages, OR... nothing
String line;
int answer = 0;
for(int i = 0; i < 50; i++)
{
line = _GetLine(sudoFile);
if(line.StartsWith("gimmipass"))
{
answer = 1;
break;
}
else if(line.StartsWith("sudo:") || line.StartsWith("no passwd entry for"))
{
answer = 2;
break;
}
}
// here we have :
switch(answer)
{
// NO PASSWORD REQUIRED
case 0:
// we don't need any status from sudo
line = "";
break;
// PASSWORD REQUIRED
case 1:
// send the password to sudo
Sleep(100);
if(password != "")
_PutLine(sudoFile, ~password);
else
_PutLine(sudoFile, "<NULL>");
// hmmmm... it hangs sometimes here reading the status
// it seems that sudo sometimes ask for password AND don't put
// status back to terminal, OR something very wrong happens
// on forks above. As is better to not have a status than
// have the app hanging, we set a timeout on status read
// blocking stream again, to be sure to get the status back
// (we know that must come 2 lines of status...)
fcntl(masterPty, F_SETFL, fcntl(masterPty, F_GETFL) & ~O_NONBLOCK);
// skip the empty line after password
_GetLine(sudoFile);
// stop blocking again, we must clean sudo output by removing
// all chars there
fcntl(masterPty, F_SETFL, fcntl(masterPty, F_GETFL) | O_NONBLOCK);
// give sudo some time to setup the status line, IF ANY
fd_set rfds;
struct timeval tv;
FD_ZERO(&rfds);
FD_SET(masterPty, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 200000;
select(masterPty + 1, &rfds, NULL, NULL, &tv);
// get the status
line = _GetLine(sudoFile);
break;
// USER DOESN'T EXISTS IN SUDOERS -- ERROR
case 2:
// close streams and leave with error
fclose(sudoFile);
return(false);
} // switch
// here we have in 'line' the status got back from sudo
// or an empty line, if password wasn't required
// we check the status for password correctness
if(
line.StartsWith("Sorry, try again.") ||
line.StartsWith("Sorry, user ")
)
{
fclose(sudoFile);
return false;
}
else
{
int spc = line.Find(' ');
if(spc >= 0 && line.Find("is not in", spc+1) >= 0)
{
fclose(sudoFile);
return false;
}
}
// ok, now we should have checked all and the sudo cmd
// should have been succesful
int status;
bool res = true;
// if requested, wait for child process to terminate
if(w)
{
// it shall terminate sometimes....
waitpid(pid, &status, 0);
// check sudo exit status
if(WIFEXITED(status))
res = (WEXITSTATUS(status) == 0);
else if(WIFSIGNALED(status))
res = true;
else
res = false;
}
// otherwise, just check once if process exited, by error
// for example -- maybe it couldn't spawn command
else
{
while(true)
{
// get spawned process executable file name
FileIn f("/proc/" + FormatInt(pid) + "/cmdline");
String procName = GetFileTitle(f.GetLine());
f.Close();
// check if process is still running
// it should, if all is ok
int waitRes = waitpid(pid, &status, WNOHANG);
// if still running within sudo, continue waiting
if(!waitRes && procName == "sudo")
{
Sleep(100);
continue;
}
// otherwise, if process exited, return its status
else if(waitRes)
{
// check sudo exit status
if(WIFEXITED(status))
res = (WEXITSTATUS(status) == 0);
else if(WIFSIGNALED(status))
res = true;
else
res = false;
return res;
}
// otherwise, process is still running as spawned app
// all is ok, just return true
return true;
}
}
// eat last sudo ouptut, if any
while(_GetLine(sudoFile) != "")
;
// close pseudoterminal
fclose(sudoFile);
return res;
}
}
#endif
END_UPP_NAMESPACE