Tiny GroovyServ

GroovyServ is an interesting solution for executing multiple Groovy scripts in a shared JVM. Unfortunately, as mentioned in this thread, restrictions in relation to the supported operating systems can become a problem.

As an alternative approach, a small SSH server was written using Groovy. In this way, any native SSH client would be able to send scripting execution commands to the server. The idea follows the same line of reasoning of the combination of ‘groovy-l’ and telnet. For handling the SSH protocol, Apache Mina SSHD was used.

It is noteworthy that the server is developed as a distant cousin, not optimized, insecure and functionally limited of GroovyServ, not having the intention of replacing it. For the execution in unrestricted environments, the use of a SecurityManager properly configured and the adoption of appropriate authentication mechanism are recommended.

Step-by-step instructions for use:

1. Download the source code of the server

2. Unzip the archive into a directory

3. Start the server

[user@techdm tiny-groovyserv]$ groovy tgserv.groovy

or

[user@techdm tiny-groovyserv]$ groovy tgserv.groovy 8123

4. Write some scripts on the same host

5. Start multiple command prompts or shells and simultaneously run the created scripts

[user@techdm test]$ ssh -p 8123 localhost /tmp/test/a.groovy abc 123

[user@techdm dir1]$ ssh -p 8123 localhost /tmp/test/dir1/b.groovy

[user@techdm dir2]$ ssh -p 8123 localhost /tmp/test/dir2/c.groovy

or using plink:

C:\tmp\test> plink -P 8123 -ssh -l x localhost c:/tmp/test/a.groovy abc 123

C:\tmp\test\dir1> plink -P 8123 -ssh -l x localhost c:/tmp/test/dir1/b.groovy

C:\tmp\test\dir2> plink -P 8123 -ssh -l x localhost c:/tmp/test/dir2/c.groovy

Project on GitHub.

Additional references:

/*
 * http://www.apache.org/licenses/LICENSE-2.0
 */

/**
 * @author Daniel Henrique Alves Lima
 */
import java.io.IOException;

import groovy.lang.GroovyClassLoader
import org.codehaus.groovy.runtime.StackTraceUtils

import org.apache.sshd.SshServer
import org.apache.sshd.server.auth.UserAuthNone
import org.apache.sshd.server.Command
import org.apache.sshd.server.CommandFactory
import org.apache.sshd.server.command.ScpCommandFactory
import org.apache.sshd.server.Environment
import org.apache.sshd.server.ExitCallback
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider

//import org.apache.sshd.server.PasswordAuthenticator
//import org.apache.sshd.server.PublickeyAuthenticator

SshServer sshd = SshServer.setUpDefaultServer()
sshd.port = args && args.length > 0? args[0] as int : 8123
sshd.keyPairProvider = new SimpleGeneratorHostKeyProvider('hostkey.ser')

def sysOut = System.out

def findMyClassLoader = {ClassLoader cl = Thread.currentThread().contextClassLoader ->
    while (cl != null && !(cl instanceof MyGroovyClassLoader)) {
        cl = cl.parent
    }
    return cl
}

sshd.commandFactory = new ScpCommandFactory(
    {command ->

        command = command.tokenize(' ')
        def cmd
        cmd = [
            input: null, output: null, error:null, callback: null,
            setExitCallback: {cb -> cmd.callback = cb},
            setInputStream: {is -> cmd.input = is as InputStream},
            setOutputStream: {out -> cmd.output = new PrintStream(out)},
            setErrorStream: {err -> cmd.error = new PrintStream(err)},
            destroy: {},
            start: {env ->
                Thread currentThread = Thread.currentThread()
                sysOut.println "${currentThread} begin"
                def oldThreadCl = currentThread.contextClassLoader
                def cl = new MyGroovyClassLoader(oldThreadCl? oldThreadCl : this.class.classLoader)
                cl.errorStream = cmd.error; cl.inputStream = cmd.input; cl.outputStream = cmd.output
				
                def scriptName = command.size() > 0?"${command[0]}":''
                def scriptArgs = (command.size() > 1? command.subList(1, command.size()): []) as String[]
                ThreadGroup tg = new ThreadGroup(currentThread.threadGroup, 
                                                 "${currentThread.threadGroup.name}:${scriptName}")
                Thread t = new Thread(tg,
                                      {
                                          long time = -1; int result = -1
                                          try {							
                                              def scriptFile = new File(scriptName)
                                              if (scriptFile.parentFile) {cl.addURL(scriptFile.parentFile.toURI().toURL())}
                                              def script = cl.parseClass(scriptFile)
                                              assert cl.equals(findMyClassLoader(script.classLoader))
                                              time = System.currentTimeMillis()
                                              script = script.newInstance()
                                              script.args = scriptArgs; script.run()
                                              result = 0
                                          } catch (Exception e) {
                                              e =  StackTraceUtils.deepSanitize(e)
                                              sshd.log.error("Error running script ${scriptName}", e)
                                              e.printStackTrace()
                                              throw e
                                          } finally {
                                              try {
                                                  while (tg.activeCount() > 1 || tg.activeGroupCount() > 1) {Thread.sleep 100}
                                                  System.err.flush(); System.out.flush()
                                                  cl.release()
                                              } finally {
                                                  if (time > 0) {time = System.currentTimeMillis() - time; sysOut.println "${Thread.currentThread()} ${time} (ms)"}
                                                  sysOut.println "${Thread.currentThread()} end"
                                                  cmd.callback.onExit(result)
                                              }
                                          }
                                      }
                                      as Runnable
                                     )
                t.contextClassLoader = cl
                t.start()	
            }
        ]
        
        return cmd as Command

    } as CommandFactory
)

/*sshd.publickeyAuthenticator = {username, key, session ->
    return true
    } as PublickeyAuthenticator*/


/*sshd.passwordAuthenticator = {username, password, session ->
    return true
    } as PasswordAuthenticator*/


def userAuthFactories = sshd.userAuthFactories
if (!userAuthFactories) {userAuthFactories = []}
userAuthFactories << new UserAuthNone.Factory()
sshd.userAuthFactories = userAuthFactories


System.err = new PrintStream(new MyOutputStream(System.err, findMyClassLoader))
System.out = new PrintStream(new MyOutputStream(System.out, findMyClassLoader))
System.in = new MyInputStream(System.in, findMyClassLoader)

sshd.start()


class MyGroovyClassLoader extends GroovyClassLoader {

    def inputStream
    def outputStream
    def errorStream
    
    public MyGroovyClassLoader() {
        super()
    }

    public MyGroovyClassLoader(ClassLoader loader) {
        super(loader)
    }

    public MyGroovyClassLoader(GroovyClassLoader parent) {
        super(parent)
    }
    
    public void release() {
        errorStream?.flush(); outputStream?.flush()
        errorStream?.close(); outputStream?.close(); inputStream?.close()
    }
    
}

class MyInputStream extends InputStream {
    
    private InputStream input
    private Closure selectCl
    
    public MyInputStream(InputStream input, Closure selectCl) {this.input = input; this.selectCl = selectCl}

    @Override
    public int read() throws IOException {
        def cl = selectCl()
        return (cl? cl.inputStream : input).read()
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        def cl = selectCl()
        return (cl? cl.inputStream : input).read(b, off, len)
    }
          
}

class MyOutputStream extends OutputStream {
    
    private OutputStream output
    private Closure selectCl
    
    public MyOutputStream(OutputStream output, Closure selectCl) {this.output = output; this.selectCl = selectCl}

    @Override
    public void write(int b) throws IOException {
        def cl = selectCl()
        (cl? cl.outputStream : output).write(b)
    }
    
    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        def cl = selectCl()
        (cl? cl.outputStream : output).write(b, off, len)
        flush() //
    }

    @Override
    public void flush() throws IOException {
        def cl = selectCl()
        (cl? cl.outputStream : output).flush()
    }
    
}

Leave a Reply

Your email address will not be published. Required fields are marked *

*


seven × 5 =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>