I was tempted to use jetty as a lightweight server to resolve the JVM-fork() memory problem as I already posted however nodejs provides a lighter and so from my simplistic design poit of view better alternative.
Below is the code for such a server:
#!/usr/local/bin/node /* ** shell-server.js returns json response with the stdout and stderr of a shell command ** ** ** @Author: Nestor Urquiza ** @Date: 09/29/2011 ** */ /* * Dependencies */ var http = require('http'), url = require('url'), exec = require('child_process').exec; /* * Server Config */ var host = "127.0.0.1", port = "8088", thisServerUrl = "http://" + host + ":" + port; /* * Main */ http.createServer(function (req, res) { req.addListener('end', function () { }); var parsedUrl = url.parse(req.url, true); var cmd = parsedUrl.query['cmd']; var async = parsedUrl.query['async']; res.writeHead(200, {'Content-Type': 'text/plain'}); if( cmd ) { var child = exec(cmd, function (error, stdout, stderr) { var result = '{"stdout":' + stdout + ',"stderr":"' + stderr + '","cmd":"' + cmd + '"}'; res.end(result + '\n'); }); } else { var result = '{"stdout":"' + '' + '","stderr":"' + 'cmd is mandatory' + '","cmd":"' + cmd + '"}'; res.end(result + '\n'); } if(async == "true") { var result = '{"stdout":"async request' + '' + '","stderr":"' + '' + '","cmd":"' + cmd + '"}'; res.end(result + '\n'); } }).listen(port, host); console.log('Server running at ' + thisServerUrl );
Once the server is running you can hit the below URL to get a list of the users loged in a OSX/linux/unix box:
http://localhost:8088/?cmd=whoHere is how to run a sleep command and a touch command demonstrating the usage of async=true which basically will run the commands but will not check for stdout nor stderr. You can see the response comes back instantaneously however the file will be touched 5 seconds later:
$ ls --full-time /tmp/here -rw-r--r-- 1 dev dev 0 2014-03-06 16:17:07.546398632 -0500 /tmp/here $ date Thu Mar 6 16:17:08 EST 2014 $ curl "http://localhost:8088/?cmd=sleep%2010;%20touch%20/tmp/here&async=true" {"stdout":"async request","stderr":"","cmd":"sleep 10; touch /tmp/here"} $ date Thu Mar 6 16:17:12 EST 2014 $ ls --full-time /tmp/here -rw-r--r-- 1 dev dev 0 2014-03-06 16:17:20.602399455 -0500 /tmp/here
Development environment
If you are running Windows you should download the node executable and run the server as:node c:\shell-server.js
If you are running OSX/Linux/Unix you have to install node make the script executable and run the below:
/path/to/shell-server.sh
Production deployment
You need to be sure the server runs as a daemon and that it restarts if it fails to serve. In debian/Ubuntu + monit you can follow these steps:$ sudo vi /opt/nodejs/shell-server.js $ sudo vi /etc/init/shell-server.conf #!/sbin/upstart description "shell server runs a command specified by HTTP GET param 'cmd'" author "admin" start on startup stop on shutdown script #export HOME="/root" #exec sudo -u admin /usr/local/bin/node /opt/nodejs/shell-server.js #exec su -c "/usr/local/bin/node /opt/nodejs/shell-server.js" dev exec start-stop-daemon --start -c dev --exec /usr/local/bin/node /opt/nodejs/shell-server.js end script $ sudo vi /etc/monit/monitrc ... ################################################################# # shell-server ################################################################ check host shell-server with address 127.0.0.1 start = "/sbin/start shell-server" stop = "/sbin/stop shell-server" if failed port 8088 protocol HTTP request / with timeout 10 seconds then restart group server ... $ sudo monit reload $ sudo vi /opt/nodejs/shell-server.js $ wget http://localhost:8088 -O -Note that upstart will log stdout and stderr to /var/log/upstart/shell-server.log
7 comments:
?cmd=cat /etc/passwd
?cmd=passwd //what happens?
?cmd=curl |sh
?cmd=rm -rf * ../ ../../
@Kenan it will happen the same as if you write e thise commands from Runtime.exec(). Security should be handled of course but that is out of tge scope of this post which is just about running commamds from Java without paying a high toll in memory consumption.
Very good, could you use crypto to encrypt and decrypt the commands?
@Ross the main motivation for this solution was to locally execute commands. Definitely support for SSL is never a bad idea even if running local commands so I would definitely vote for using crypto to support HTTPS instead of plain HTTP.
Great!! simple && powerful && controlrable!!
Instead of exec, you could also exec the commands over the same background process(es) for stateful behavior; see https://github.com/bitsofinfo/stateful-process-command-proxy
Post a Comment