/*
JythonServer.java
The JythonServer class is used to provide a tcp port on the
Ganymede server which can be telnetted to in order to interact with
a Python (Jython) console.
Created: 19 July 2004
Module By: Deepak Giridharagopal, deepak@brownman.org
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2013
The University of Texas at Austin
Ganymede is a registered trademark of The University of Texas at Austin
Contact information
Web site: http://www.arlut.utexas.edu/gash2
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.RemoteException;
import java.util.Arrays;
import org.python.core.PyException;
import org.python.core.PySystemState;
import org.python.util.InteractiveConsole;
import arlut.csd.Util.TranslationService;
/*------------------------------------------------------------------------------
class
JythonServer
------------------------------------------------------------------------------*/
/**
* <p>The JythonServer class is used to provide a tcp port on the
* Ganymede server which can be telnetted to in order to interact with
* a Python (Jython) console.</p>
*
* @author Deepak Giridharagopal deepak@brownman.org
*/
public class JythonServer extends Thread {
/* The server's listener socket */
private ServerSocket sock = null;
/* boolean flag for indicating that we should stop listening for new
* connections */
private boolean shutdownRequested = false;
/* Lock to synchronize on when changing the shutdown flag */
private Object lock = new Object();
public JythonServer()
{
super("JythonServer");
}
public void run(int portNumber)
{
try
{
listen(portNumber);
}
catch (IOException ex)
{
Ganymede.logError(ex);
}
}
private void listen(int portNumber) throws IOException
{
Socket s;
Thread t;
PySystemState.initialize();
try
{
sock = new ServerSocket(portNumber);
}
catch (IOException e)
{
System.err.println("Could not listen on port: 4444.");
return;
}
while (true)
{
s = sock.accept();
synchronized (lock)
{
if (shutdownRequested)
{
break;
}
}
t = new JythonServerWorker(s, new JythonServerProtocol(s));
t.start();
}
sock.close();
}
public void shutdown()
{
synchronized (lock)
{
shutdownRequested = true;
}
/* Now close the ServerSocket */
try
{
new Socket(sock.getInetAddress(), sock.getLocalPort()).close();
}
catch (IOException ex)
{
Ganymede.stackTrace(ex);
}
}
}
/*------------------------------------------------------------------------------
class
JythonServerProtocol
------------------------------------------------------------------------------*/
/**
* <p>Implementation of the server I/O protocol. Principally, it reads
* in lines of input from the client and execs them inside a Jython
* interpreter.</p>
*
* @author Deepak Giridharagopal deepak@brownman.org
*/
class JythonServerProtocol {
/**
* <p>TranslationService object for handling string localization in
* the Ganymede server.</p>
*/
private static TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.JythonServerProtocol");
public static String doneString = null;
/**
* <p>The session associated with this telnet connection.</p>
*/
public GanymedeSession session;
private Socket socket;
private InteractiveConsole interp;
private StringWriter buffer;
private String prompt = ">>> ";
public JythonServerProtocol(Socket sock)
{
JythonServerProtocol.doneString = ts.l("global.done");
this.socket = sock;
buffer = new StringWriter(64);
interp = new InteractiveConsole();
interp.setOut(buffer);
interp.setErr(buffer);
/* Import the additional Jython library routines */
interp.exec("import sys");
interp.exec("sys.path.append( sys.prefix + '" + System.getProperty("file.separator") + "' + 'jython-lib.jar' )");
/* Seed the interpreter with a pointer to important Ganymede classes */
interp.exec("from arlut.csd.ganymede.server import *");
interp.exec("from arlut.csd.ganymede.common import *");
}
public void createSession(DBObject personaObj, DBObject userObj)
{
try
{
session = new GanymedeSession(personaObj.getLabel(), userObj, personaObj, false, false);
}
catch (RemoteException ex)
{
Ganymede.stackTrace(ex);
return;
}
interp.set("session", session);
}
public String processInput(String input)
{
String output;
boolean moreInputRequired;
if (input == null)
{
// '\nHello {0}\nWelcome to the Ganymede Jython interpreter!\n\nType "quit" to exit.\n{1}'
return ts.l("processInput.greeting", socket.getInetAddress().getHostAddress(), prompt);
}
if (input.equals(ts.l("processInput.quitcommand")))
{
return doneString;
}
try
{
moreInputRequired = interp.push(input);
if (moreInputRequired)
{
return "... ";
}
buffer.flush();
output = buffer.toString();
interp.resetbuffer();
buffer.getBuffer().setLength(0);
}
catch (PyException pex)
{
output = buffer.toString() + "\n" + pex.toString();
interp.resetbuffer();
buffer.getBuffer().setLength(0);
}
return output + prompt;
}
}
/*------------------------------------------------------------------------------
class
JythonServerWorker
------------------------------------------------------------------------------*/
/**
* <p>Handles passing input and output to the above Protocol
* class.</p>
*
* @author Deepak Giridharagopal deepak@brownman.org
*/
class JythonServerWorker extends Thread {
/**
* <p>TranslationService object for handling string localization in
* the Ganymede server.</p>
*/
private static TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.JythonServerWorker");
private Socket socket = null;
private JythonServerProtocol protocol = null;
public JythonServerWorker(Socket socket, JythonServerProtocol protocol)
{
super("JythonServerWorker");
this.socket = socket;
this.protocol = protocol;
}
public void run()
{
try
{
OutputStream rawOutput = socket.getOutputStream();
InputStream rawInput = socket.getInputStream();
PrintWriter out = new PrintWriter(rawOutput, true);
BufferedReader in = new BufferedReader(new InputStreamReader(rawInput));
String inputLine, outputLine;
/* Check to make sure that this telnet session is from localhost */
InetAddress clientAddress = socket.getInetAddress();
if (!clientAddress.equals(java.net.InetAddress.getByName(Ganymede.serverHostProperty))
&& !clientAddress.getHostAddress().equals("127.0.0.1"))
{
// Permission denied
out.println(ts.l("run.denied_not_localhost"));
out.flush();
socket.close();
return;
}
/* Check to see if logins are allowed */
String error = Ganymede.server.lSemaphore.increment();
if (error != null)
{
if (error.equals("shutdown"))
{
// "ERROR: The server is currently waiting to shut down. No logins will be accepted until the server has restarted"
out.print(ts.l("run.nologins_shutdown"));
}
else
{
// "ERROR: Can't log in to the Ganymede server.. semaphore disabled: {0}"
out.print(ts.l("run.nologins_semaphore", error));
}
out.print("\n");
out.flush();
socket.close();
return;
}
try
{
// "Username"
out.print(ts.l("run.username"));
out.print(": ");
out.flush();
String loginName = in.readLine();
/* Telnet terminal codes */
/* IAC WILL ECHO */
byte[] echoOff = { (byte)255, (byte)251, (byte)1 };
/* IAC DO ECHO */
byte[] echoOffResponse = { (byte)255, (byte)253, (byte)1 };
/* IAC WONT ECHO */
byte[] echoOn = { (byte)255, (byte)252, (byte)1 };
/* IAC DONT ECHO */
byte[] echoOnResponse = { (byte)255, (byte)254, (byte)1 };
/* Holds the client response to each terminal code */
byte[] responseBuffer = new byte[3];
// "Password"
out.print(ts.l("run.password"));
out.print(": ");
out.flush();
/* Disable client-side character echo while the user types in
* the password */
rawOutput.write(echoOff);
rawOutput.flush();
int chars_read = 0;
while (chars_read < 3)
{
chars_read += rawInput.read(responseBuffer, chars_read, 3-chars_read);
}
if (!Arrays.equals(responseBuffer, echoOffResponse))
{
out.print("Your telnet client won't properly suppress character echo.");
out.flush();
socket.close();
return;
}
String password = in.readLine();
/* Now re-enable client-side character echo so we can conduct
* business as usual */
rawOutput.write(echoOn);
rawOutput.flush();
chars_read = 0;
while (chars_read < 3)
{
chars_read += rawInput.read(responseBuffer, chars_read, 3-chars_read);
}
if (!Arrays.equals(responseBuffer, echoOnResponse))
{
out.print("Your telnet client won't properly resume character echo.");
out.flush();
socket.close();
return;
}
/* Authenticate the user */
DBObject personaObj = Ganymede.server.validateAdminLogin(loginName, password);
int validationResult = Ganymede.server.validateConsoleAdminPersona(personaObj);
/* A result of 3 means that this user has interpreter access
* privileges. Anything else means that we give 'em the boot. */
if (validationResult != 3)
{
try
{
Thread.currentThread().sleep(3000);
}
catch (InterruptedException ex)
{
/* Move along */
}
// "Permission denied."
out.print(ts.l("run.denied"));
out.print("\n");
out.flush();
socket.close();
return;
}
/* Send the HELO */
outputLine = protocol.processInput(null);
out.print(outputLine);
out.flush();
/* Setup the interpreter session variable */
DBObject userObj = Ganymede.server.getUserFromPersona(personaObj);
protocol.createSession(personaObj, userObj);
/* Here is the read-eval-print loop */
while ((inputLine = in.readLine()) != null)
{
outputLine = protocol.processInput(inputLine);
out.print(outputLine);
out.flush();
if (outputLine.equals(JythonServerProtocol.doneString))
break;
}
out.close();
in.close();
socket.close();
}
finally
{
Ganymede.server.lSemaphore.decrement();
/* Make sure to register the logout */
try
{
protocol.session.logout();
}
catch (Exception e)
{
Ganymede.logError(e); // Move along
}
}
}
catch (IOException e)
{
Ganymede.logError(e);
}
}
}