Standard input through sudo - java

I'm writing a Java program that calls other programs, and some of them need to be ran as root.
I thought about running the whole JAR as root but that sounds like a security risk, I'd rather run just some programs as root.
Currently, I create a ProcessBuilder and use its getStandardOutput method to instantiate a Writer, on the command sudo -S pacman <...>. (-S means ‘read from standard input’).
I then inject the password using that reader and flush it, so far so good.
However, when pacman expects input (for example when asking an interactive yes/no question), and I try to write to the Writer again, it fails with IOException because the stream is closed.
How can I get a Writer that can actually send characters to pacman and other programs ran as root?
This program is only expected to run on UNIX (Arch Linux, Debian, MacOS and maybe Window's WSL) so UNIX-specific solutions are fine, if possible I'd rather not use JNI because it requires to compile multiple times, but if that's the only solution it's fine. I'm also fine replacing sudo with something else as long as it's still ran as root.

If this is not a long-lived service, you could run sudo -v to have sudo cache the credentials and then invoke sudo in your Java program without needing to enter a password. This would work best if you invoked your Java program with something like:
#!/bin/bash
sudo -v
java -jar ...

Related

How do I sequentially execute wsl commands from java?

I want to execute wsl commands from java.
I'm trying to do this using Process and ProcessBuilder.
As I understand, there are two ways to do this:
Run wsl along with command as argument (for example: wsl ls -l) (do this per-command).
Run wsl, and then execute the commands one by one.
But there are some problems with 1 and 2.
With point 1:
When the command terminates, the process does not stop. So even if i run wsl ls, I can not determine the moment when I can call next command.
wsl does not save the state between such calls, so it's not very convenient
With point 2:
Since wsl does not show bash prompt, I can not track when the command stopped displaying information. For example:
ProcessBuilder pb = new ProcessBuilder("wsl");
pb.redirectErrorStream(true);
Process p = pb.start();
Thread.sleep(1000);
OutputStreamWriter osw = new OutputStreamWriter(p.getOutputStream());
osw.write("ls\n");
osw.flush();
And all I can read is:
build.gradle
gradle
gradlew
gradlew.bat
out
settings.gradle
src
No selya#selya-pc:/mnt/c/Users/selya$ read. So I can't use it as a separator between commands. I think that wsl somehow tracks, in what environment it was launched, for example, through isatty(), and therefore the output is different.
With both:
Аs far as I know, for programs that are running outside the terminal, stdout is not buffered. So there are some problems with, for example, sudo, because it asks fro a password without newline/flush ([sudo] password for selya:), therefore I can't read this line...
I found a solution - pass command as argument to unbuffer util, for example:
wsl unbuffer -p sudo apt-get update
(-p stands for pipeline).
But other problems still remain. Is there any way to do this? Or maybe there is lib for it? Even c or c++ lib will suit my needs...
P.S. I tried to find a solution for several days. Russian-speaking SO didn't help me, that's why I'm here. Sorry for my English...
The problem was solved with the help of pty4j. It works on linux, mac and windows. (It works like pseudo terminal on linux, but have nice java interface).

Sudo in Java's Runtime.exec & gksu for umount

I am writing a Java application in which (among other stuff) I'd mount a external device, do some copying, and then unmount it.
//I am mounting several devices in created dirs named sdb, sdc... according to the partitions
String[] command = {"gksu", "mount", "/dev/sd" + letter + "1", "mounter/sd" + letter};
Runtime.getRuntime().exec(command);
This works fine both in the terminal and in my program.
To unmount faster, I thought about using umount -a but gksu umount -a doesn't work in the terminal and consequently not in the Java program. sudo umount -a does work in the terminal, but not in the application. Meanwhile, I got it to work by unmounting the devices 1 by 1, but it would be cleaner if I could get umount -a to work someway.
If you understand why either gksu doesn't work with umount or sudo with Runtime.exec(), I'd take your explanation.
Thanks
I feel the problem is sudo not asking me for a password, as gksu does. But I don't know how to give it a password.
This is very likely the case.
There are a couple of different possible situations here, and each I think has it's own solution:
The user running the program (in the case of a desktop app) already has privileges to run the commands you need. - Prompt the user to enter their password and pass it to sudo through stdin using the -s flag. Check out the sudo man page for more. This is simple and ensures that your application doesn't have greater access than the user running it.
If your application needs to run with different privileges than the user has, or if this is running on a server, then the application should be run as it's own System User. You can then use visudo to give that system user the ability to run ONLY the commands you need without requiring a password. Just be very cautious about editing the sudoers file. I recommend adding it as a separate file and just linking to it in the actual sudoers so that it's easier to undo later.

Execute tcl using sudo command

I am using tcl.lang.Interp.eval() to execute a "hello world" tcl script. The command used to execute the script is something like this-
source /path/of/my/script.tcl
Now how do I execute the script as sudo user as we cannot execute source using sudo through java program?
You can run tclsh (the “mothership” implementation of Tcl, written in C) from inside sudo just fine, or you can run a JVM which uses the TclJava library. However, sudo runs whole processes with elevated privileges and not just a library; you need to think in terms of creating a program that will do what you want, possibly with suitable arguments passed in.
FWIW, I'd start by trying:
sudo tclsh /path/of/my/script.tcl
That's going to be the simplest if it works; it's how tclsh is designed to be used (plus sudo). It's only unsuitable if the script requires access to an in-process JVM. If that's the case, you're probably going to have to write a small wrapper Java program.

Execute shell command in Tomcat

So I have the following problem: I have a web service running inside a Tomcat7 server on Linux. The web service however has to execute some commands (mostly file operations such as copy and mount). Copy I've replaced with java.nio, but I don't think that there is a replacement for mount.
So I'm trying to execute shell commands out of my Tomcat Java process. Unfortunately it doesn't execute my commands. I've implemented the execution of shell commands in Java before. So my code should be correct:
Process pr = Runtime.getRuntime().exec("mount -o loop -t iso9660 <myimage> <mymountpoint>");
pr.waitFor();
<myimage> and <mymountpoint> are absolute paths, so no issues there either.
I've debugged my commands and they are working when executed on the console.
I've tried sending other commands. Simple commands such as id and pwd are working!
I've tried using /bin/bash -c "<command>", which didn't work.
I've tried executing a shell script, which executes the command, which didn't work.
I've tried escaping the spaces in my command, which didn't work.
So I've digged even deeper and now I'm suspecting some Tomcat security policy (Sandbox?), which prevents me from executing the command. Since security is no issue for me (it's an internal system, completely isolated from the outside world), I've tried a hack, which became quite popular just recently:
System.setSecurityManager(null);
This didn't work either. I'm using Java7 and Tomcat7 on RHEL6. Tomcat7 is just extracted! I don't have any files in /etc/.. or any other folder than /opt/tomcat/, where I've extracted the zip from the Tomcat home page. I've searched the /opt/tomcat/conf folder for security settings, but all I could find was the file catalina.policy, where it didn't seem like I could set some security level for shell commands.
Any ideas?
A few things:
System.setSecurityManager(null);
you have just killed the security of your application.
Yes, Tomcat is running as root. If I execute id I'm root as well.
Fix this immediately!
Now on to the question. You shouldnt have Tomcat executing anything, you need to defer this to a separate process whether that be a shell script or another Java program. This should also remove what (I hope) was a dependency on root running Tomcat. It should be possible to perform this command as a non-privileged user that cannot log into the system normally. You would do this by configuring /etc/fstab and supplying that same user the permissions to do this. From a pure security POV the process that mounts should not be owned by the tomcat user. Nor should the tomcat user ever be root. So to recap:
1) Stop running Tomcat as root
2) Create a separate process outside of the context of Tomcat to run this mount
3) Create a tomcat user, this user should not be able to log into the system nor should it be a privileged user (admin,super user, etc)
4) Create a process user, this user should be configured exactly as the tomcat user
5) Edit /etc/fstab giving the process user the necessary permissions to mount correctly.
It's generally a bad idea to use the single-string form of Runtime.exec. A better option is to use ProcessBuilder, and split up the arguments yourself rather than relying on Java to split them for you (which it does very naïvely).
ProcessBuilder pb = new ProcessBuilder("/bin/mount", "-o", "loop", /*...*/);
pb.redirectErrorStream(true); // equivalent of 2>&1
Process p = pb.start();
You say you're on RHEL so do you have selinux active? Check your logs and see if this is what's blocking you (I think it's audit.log you're looking for, it's been a few years since I've used selinux). If this does turn out to be the problem then you should probably ask on superuser or serverfault rather than SO...
I'm not sure if that's the problem you are having, but I've seen issues when Runtime.exec() is used without reading the associated output buffers. You can find a detailed explanation and potential solutions here. Reading the output and error streams can also help you figure out what's going on at the OS level when you run the command.
I've recently had to do something like this from a Swing app.
You'll probably be able to pull it off with ProcessBuilder, as in Ian's answer, but I found that once things start to get complex, it's easier to write a shell script that does what you want, enabling you to pass as few parameters as possible. Then use ProcessBuilder to invoke the shell script.
If you're invoking anything that has more than really minimal output, you'll also have to read the output and error streams to keep the process from blocking when the output buffers fill, as it seems you are already doing.
I use sudo -S before command and for the tomcat7 user: tomcat7 ALL=(ALL) NOPASSWD:ALL

What are the differences between Java's java.lang.Runtime.exec() and PHP's exec()?

The following doesn't work in Java (an exception is thrown):
Runtime.getRuntime().exec("cd mydir; myprog");
The same works fine in PHP:
exec("cd mydir; myprog");
What exactly is different in Java's implementation and why (it seems more limited at first glance)?
the java exec command does not use the system command interpreter. something like "cd mydir; myprog" depends on the system command line interpreter (e.g. on windows cmd, on linux sh) to split that into 2 separate commands and execute each of them. java does not invoke the system command interpreter, so that does not work. you either need to call each command separately, or invoke the desired interpreter yourself as part of the command line.
I've seen people have problems like this, and I'm sure there are several ways, however the one I've seen most people reply is this. add cmd before it.
Runtime.getRuntime().exec("cmd cd mydir; myprog");
Assuming you're running an applet, not Java in a CLI environment on the server? If so, then your Java runtime is running on the client computer, not the server.
Java also has a better way to handle multiple commands than your semicolon. Instead of using the signature:
Runtime.exec(String)
try using this for each of your commands:
Runtime.exec(String[])
and make each argument of your command an element in the String array.

Categories