Merged with 549

This commit is contained in:
Nachi Ueno 2011-01-12 18:46:01 +09:00
commit 0a33f1ed87
31 changed files with 3857 additions and 2 deletions

137
bin/nova-ajax-console-proxy Executable file
View File

@ -0,0 +1,137 @@
#!/usr/bin/env python
# pylint: disable-msg=C0103
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Ajax Console Proxy Server"""
from eventlet import greenthread
from eventlet.green import urllib2
import exceptions
import gettext
import logging
import os
import sys
import time
import urlparse
# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags
from nova import log as logging
from nova import rpc
from nova import utils
from nova import wsgi
FLAGS = flags.FLAGS
flags.DEFINE_integer('ajax_console_idle_timeout', 300,
'Seconds before idle connection destroyed')
LOG = logging.getLogger('nova.ajax_console_proxy')
LOG.setLevel(logging.DEBUG)
LOG.addHandler(logging.StreamHandler())
class AjaxConsoleProxy(object):
tokens = {}
def __call__(self, env, start_response):
try:
req_url = '%s://%s%s?%s' % (env['wsgi.url_scheme'],
env['HTTP_HOST'],
env['PATH_INFO'],
env['QUERY_STRING'])
if 'HTTP_REFERER' in env:
auth_url = env['HTTP_REFERER']
else:
auth_url = req_url
auth_params = urlparse.parse_qs(urlparse.urlparse(auth_url).query)
parsed_url = urlparse.urlparse(req_url)
auth_info = AjaxConsoleProxy.tokens[auth_params['token'][0]]
args = auth_info['args']
auth_info['last_activity'] = time.time()
remote_url = ("http://%s:%s%s?token=%s" % (
str(args['host']),
str(args['port']),
parsed_url.path,
str(args['token'])))
opener = urllib2.urlopen(remote_url, env['wsgi.input'].read())
body = opener.read()
info = opener.info()
start_response("200 OK", info.dict.items())
return body
except (exceptions.KeyError):
if env['PATH_INFO'] != '/favicon.ico':
LOG.audit("Unauthorized request %s, %s"
% (req_url, str(env)))
start_response("401 NOT AUTHORIZED", [])
return "Not Authorized"
except Exception:
start_response("500 ERROR", [])
return "Server Error"
def register_listeners(self):
class Callback:
def __call__(self, data, message):
if data['method'] == 'authorize_ajax_console':
AjaxConsoleProxy.tokens[data['args']['token']] = \
{'args': data['args'], 'last_activity': time.time()}
conn = rpc.Connection.instance(new=True)
consumer = rpc.TopicConsumer(
connection=conn,
topic=FLAGS.ajax_console_proxy_topic)
consumer.register_callback(Callback())
def delete_expired_tokens():
now = time.time()
to_delete = []
for k, v in AjaxConsoleProxy.tokens.items():
if now - v['last_activity'] > FLAGS.ajax_console_idle_timeout:
to_delete.append(k)
for k in to_delete:
del AjaxConsoleProxy.tokens[k]
utils.LoopingCall(consumer.fetch, auto_ack=True,
enable_callbacks=True).start(0.1)
utils.LoopingCall(delete_expired_tokens).start(1)
if __name__ == '__main__':
utils.default_flagfile()
FLAGS(sys.argv)
server = wsgi.Server()
acp = AjaxConsoleProxy()
acp.register_listeners()
server.start(acp, FLAGS.ajax_console_proxy_port, host='0.0.0.0')
server.wait()

View File

@ -78,6 +78,7 @@ if [ "$CMD" == "install" ]; then
sudo apt-get install -y user-mode-linux kvm libvirt-bin
sudo apt-get install -y screen euca2ools vlan curl rabbitmq-server
sudo apt-get install -y lvm2 iscsitarget open-iscsi
sudo apt-get install -y socat
echo "ISCSITARGET_ENABLE=true" | sudo tee /etc/default/iscsitarget
sudo /etc/init.d/iscsitarget restart
sudo modprobe kvm
@ -161,6 +162,7 @@ if [ "$CMD" == "run" ]; then
screen_it network "$NOVA_DIR/bin/nova-network"
screen_it scheduler "$NOVA_DIR/bin/nova-scheduler"
screen_it volume "$NOVA_DIR/bin/nova-volume"
screen_it ajax_console_proxy "$NOVA_DIR/bin/nova-ajax-console-proxy"
screen_it test ". $NOVA_DIR/novarc"
screen -S nova -x
fi

View File

@ -504,6 +504,11 @@ class CloudController(object):
"Timestamp": now,
"output": base64.b64encode(output)}
def get_ajax_console(self, context, instance_id, **kwargs):
ec2_id = instance_id[0]
internal_id = ec2_id_to_id(ec2_id)
return self.compute_api.get_ajax_console(context, internal_id)
def describe_volumes(self, context, volume_id=None, **kwargs):
volumes = self.volume_api.get_all(context)
# NOTE(vish): volume_id is an optional list of volume ids to filter by.

View File

@ -283,6 +283,15 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
def get_ajax_console(self, req, id):
""" Returns a url to an instance's ajaxterm console. """
try:
self.compute_api.get_ajax_console(req.environ['nova.context'],
int(id))
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted()
def diagnostics(self, req, id):
"""Permit Admins to retrieve server diagnostics."""
ctxt = req.environ["nova.context"]

View File

@ -414,7 +414,26 @@ class API(base.Base):
rpc.cast(context,
self.db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "unrescue_instance",
"args": {"instance_id": instance_id}})
"args": {"instance_id": instance['id']}})
def get_ajax_console(self, context, instance_id):
"""Get a url to an AJAX Console"""
instance = self.get(context, instance_id)
output = rpc.call(context,
'%s.%s' % (FLAGS.compute_topic,
instance['host']),
{'method': 'get_ajax_console',
'args': {'instance_id': instance['id']}})
rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic,
{'method': 'authorize_ajax_console',
'args': {'token': output['token'], 'host': output['host'],
'port': output['port']}})
return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url,
output['token'])}
def lock(self, context, instance_id):
"""

View File

@ -471,6 +471,14 @@ class ComputeManager(manager.Manager):
return self.driver.get_console_output(instance_ref)
@exception.wrap_exception
def get_ajax_console(self, context, instance_id):
"""Return connection information for an ajax console"""
context = context.elevated()
logging.debug(_("instance %s: getting ajax console"), instance_id)
instance_ref = self.db.instance_get(context, instance_id)
return self.driver.get_ajax_console(instance_ref)
@checks_instance_lock
def attach_volume(self, context, instance_id, volume_id, mountpoint):
"""Attach a volume to an instance."""

View File

@ -234,7 +234,14 @@ DEFINE_string('scheduler_topic', 'scheduler',
'the topic scheduler nodes listen on')
DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on')
DEFINE_string('network_topic', 'network', 'the topic network nodes listen on')
DEFINE_string('ajax_console_proxy_topic', 'ajax_proxy',
'the topic ajax proxy nodes listen on')
DEFINE_string('ajax_console_proxy_url',
'http://127.0.0.1:8000',
'location of ajax console proxy, \
in the form "http://127.0.0.1:8000"')
DEFINE_string('ajax_console_proxy_port',
8000, 'port that ajax_console_proxy binds')
DEFINE_bool('verbose', False, 'show debug output')
DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit')
DEFINE_bool('fake_network', False,

View File

@ -167,6 +167,19 @@ class CloudTestCase(test.TestCase):
greenthread.sleep(0.3)
rv = self.cloud.terminate_instances(self.context, [instance_id])
def test_ajax_console(self):
kwargs = {'image_id': image_id}
rv = yield self.cloud.run_instances(self.context, **kwargs)
instance_id = rv['instancesSet'][0]['instanceId']
output = yield self.cloud.get_console_output(context=self.context,
instance_id=[instance_id])
self.assertEquals(b64decode(output['output']),
'http://fakeajaxconsole.com/?token=FAKETOKEN')
# TODO(soren): We need this until we can stop polling in the rpc code
# for unit tests.
greenthread.sleep(0.3)
rv = yield self.cloud.terminate_instances(self.context, [instance_id])
def test_key_generation(self):
result = self._create_key('test')
private_key = result['private_key']

View File

@ -169,6 +169,16 @@ class ComputeTestCase(test.TestCase):
self.assert_(console)
self.compute.terminate_instance(self.context, instance_id)
def test_ajax_console(self):
"""Make sure we can get console output from instance"""
instance_id = self._create_instance()
self.compute.run_instance(self.context, instance_id)
console = self.compute.get_ajax_console(self.context,
instance_id)
self.assert_(console)
self.compute.terminate_instance(self.context, instance_id)
def test_run_instance_existing(self):
"""Ensure failure when running an instance that already exists"""
instance_id = self._create_instance()

View File

@ -155,6 +155,11 @@ def abspath(s):
return os.path.join(os.path.dirname(__file__), s)
def novadir():
import nova
return os.path.abspath(nova.__file__).split('nova/__init__.pyc')[0]
def default_flagfile(filename='nova.conf'):
for arg in sys.argv:
if arg.find('flagfile') != -1:

View File

@ -289,6 +289,9 @@ class FakeConnection(object):
def get_console_output(self, instance):
return 'FAKE CONSOLE OUTPUT'
def get_ajax_console(self, instance):
return 'http://fakeajaxconsole.com/?token=FAKETOKEN'
def get_console_pool_info(self, console_type):
return {'address': '127.0.0.1',
'username': 'fakeuser',

View File

@ -72,9 +72,22 @@
#end if
</filterref>
</interface>
<!-- The order is significant here. File must be defined first -->
<serial type="file">
<source path='${basepath}/console.log'/>
<target port='1'/>
</serial>
<console type='pty' tty='/dev/pts/2'>
<source path='/dev/pts/2'/>
<target port='0'/>
</console>
<serial type='pty'>
<source path='/dev/pts/2'/>
<target port='0'/>
</serial>
</devices>
</domain>

View File

@ -38,6 +38,11 @@ Supports KVM, QEMU, UML, and XEN.
import os
import shutil
import random
import subprocess
import uuid
from xml.dom import minidom
from eventlet import greenthread
from eventlet import event
@ -86,6 +91,9 @@ flags.DEFINE_string('libvirt_uri',
flags.DEFINE_bool('allow_project_net_traffic',
True,
'Whether to allow in project network traffic')
flags.DEFINE_string('ajaxterm_portrange',
'10000-12000',
'Range of ports that ajaxterm should randomly try to bind')
flags.DEFINE_string('firewall_driver',
'nova.virt.libvirt_conn.IptablesFirewallDriver',
'Firewall driver (defaults to iptables)')
@ -442,6 +450,43 @@ class LibvirtConnection(object):
return self._dump_file(fpath)
@exception.wrap_exception
def get_ajax_console(self, instance):
def get_open_port():
start_port, end_port = FLAGS.ajaxterm_portrange.split("-")
for i in xrange(0, 100): # don't loop forever
port = random.randint(int(start_port), int(end_port))
# netcat will exit with 0 only if the port is in use,
# so a nonzero return value implies it is unused
cmd = 'netcat 0.0.0.0 %s -w 1 </dev/null || echo free' % (port)
stdout, stderr = utils.execute(cmd)
if stdout.strip() == 'free':
return port
raise Exception(_('Unable to find an open port'))
def get_pty_for_instance(instance_name):
virt_dom = self._conn.lookupByName(instance_name)
xml = virt_dom.XMLDesc(0)
dom = minidom.parseString(xml)
for serial in dom.getElementsByTagName('serial'):
if serial.getAttribute('type') == 'pty':
source = serial.getElementsByTagName('source')[0]
return source.getAttribute('path')
port = get_open_port()
token = str(uuid.uuid4())
host = instance['host']
ajaxterm_cmd = 'sudo socat - %s' \
% get_pty_for_instance(instance['name'])
cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \
% (utils.novadir(), ajaxterm_cmd, token, port)
subprocess.Popen(cmd, shell=True)
return {'token': token, 'host': host, 'port': port}
def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None):
# syntactic nicety
basepath = lambda fname = '', prefix = prefix: os.path.join(

View File

@ -284,6 +284,11 @@ class VMOps(object):
# TODO: implement this to fix pylint!
return 'FAKE CONSOLE OUTPUT of instance'
def get_ajax_console(self, instance):
"""Return link to instance's ajax console"""
# TODO: implement this!
return 'http://fakeajaxconsole/fake_url'
def list_from_xenstore(self, vm, path):
"""Runs the xenstore-ls command to get a listing of all records
from 'path' downward. Returns a dict with the sub-paths as keys,

View File

@ -181,6 +181,10 @@ class XenAPIConnection(object):
"""Return snapshot of console"""
return self._vmops.get_console_output(instance)
def get_ajax_console(self, instance):
"""Return link to instance's ajax console"""
return self._vmops.get_ajax_console(instance)
def attach_volume(self, instance_name, device_path, mountpoint):
"""Attach volume storage to VM instance"""
return self._volumeops.attach_volume(instance_name,

120
tools/ajaxterm/README.txt Normal file
View File

@ -0,0 +1,120 @@
= [http://antony.lesuisse.org/qweb/trac/wiki/AjaxTerm Ajaxterm] =
Ajaxterm is a web based terminal. It was totally inspired and works almost
exactly like http://anyterm.org/ except it's much easier to install (see
comparaison with anyterm below).
Ajaxterm written in python (and some AJAX javascript for client side) and depends only on python2.3 or better.[[BR]]
Ajaxterm is '''very simple to install''' on Linux, MacOS X, FreeBSD, Solaris, cygwin and any Unix that runs python2.3.[[BR]]
Ajaxterm was written by Antony Lesuisse (email: al AT udev.org), License Public Domain.
Use the [/qweb/forum/viewforum.php?id=2 Forum], if you have any question or remark.
== News ==
* 2006-10-29: v0.10 allow space in login, cgi launch fix, redhat init
* 2006-07-12: v0.9 change uid, daemon fix (Daniel Fischer)
* 2006-07-04: v0.8 add login support to ssh (Sven Geggus), change max width to 256
* 2006-05-31: v0.7 minor fixes, daemon option
* 2006-05-23: v0.6 Applied debian and gentoo patches, renamed to Ajaxterm, default port 8022
== Download and Install ==
* Release: [/qweb/files/Ajaxterm-0.10.tar.gz Ajaxterm-0.10.tar.gz]
* Browse src: [/qweb/trac/browser/trunk/ajaxterm/ ajaxterm/]
To install Ajaxterm issue the following commands:
{{{
wget http://antony.lesuisse.org/qweb/files/Ajaxterm-0.10.tar.gz
tar zxvf Ajaxterm-0.10.tar.gz
cd Ajaxterm-0.10
./ajaxterm.py
}}}
Then point your browser to this URL : http://localhost:8022/
== Screenshot ==
{{{
#!html
<center><img src="/qweb/trac/attachment/wiki/AjaxTerm/scr.png?format=raw" alt="ajaxterm screenshot" style=""/></center>
}}}
== Documentation and Caveats ==
* Ajaxterm only support latin1, if you use Ubuntu or any LANG==en_US.UTF-8 distribution don't forget to "unset LANG".
* If run as root ajaxterm will run /bin/login, otherwise it will run ssh
localhost. To use an other command use the -c option.
* By default Ajaxterm only listen at 127.0.0.1:8022. For remote access, it is
strongly recommended to use '''https SSL/TLS''', and that is simple to
configure if you use the apache web server using mod_proxy.[[BR]][[BR]]
Using ssl will also speed up ajaxterm (probably because of keepalive).[[BR]][[BR]]
Here is an configuration example:
{{{
Listen 443
NameVirtualHost *:443
<VirtualHost *:443>
ServerName localhost
SSLEngine On
SSLCertificateKeyFile ssl/apache.pem
SSLCertificateFile ssl/apache.pem
ProxyRequests Off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
ProxyPass /ajaxterm/ http://localhost:8022/
ProxyPassReverse /ajaxterm/ http://localhost:8022/
</VirtualHost>
}}}
* Using GET HTTP request seems to speed up ajaxterm, just click on GET in the
interface, but be warned that your keystrokes might be loggued (by apache or
any proxy). I usually enable it after the login.
* Ajaxterm commandline usage:
{{{
usage: ajaxterm.py [options]
options:
-h, --help show this help message and exit
-pPORT, --port=PORT Set the TCP port (default: 8022)
-cCMD, --command=CMD set the command (default: /bin/login or ssh localhost)
-l, --log log requests to stderr (default: quiet mode)
-d, --daemon run as daemon in the background
-PPIDFILE, --pidfile=PIDFILE
set the pidfile (default: /var/run/ajaxterm.pid)
-iINDEX_FILE, --index=INDEX_FILE
default index file (default: ajaxterm.html)
-uUID, --uid=UID Set the daemon's user id
}}}
* Ajaxterm was first written as a demo for qweb (my web framework), but
actually doesn't use many features of qweb.
* Compared to anyterm:
* There are no partial updates, ajaxterm updates either all the screen or
nothing. That make the code simpler and I also think it's faster. HTTP
replies are always gzencoded. When used in 80x25 mode, almost all of
them are below the 1500 bytes (size of an ethernet frame) and we just
replace the screen with the reply (no javascript string handling).
* Ajaxterm polls the server for updates with an exponentially growing
timeout when the screen hasn't changed. The timeout is also resetted as
soon as a key is pressed. Anyterm blocks on a pending request and use a
parallel connection for keypresses. The anyterm approch is better
when there aren't any keypress.
* Ajaxterm files are released in the Public Domain, (except [http://sarissa.sourceforge.net/doc/ sarissa*] which are LGPL).
== TODO ==
* insert mode ESC [ 4 h
* change size x,y from gui (sending signal)
* vt102 graphic codepage
* use innerHTML or prototype instead of sarissa

35
tools/ajaxterm/ajaxterm.1 Normal file
View File

@ -0,0 +1,35 @@
.TH ajaxterm "1" "May 2006" "ajaxterm 0.5" "User commands"
.SH NAME
ajaxterm \- Web based terminal written in python
.SH DESCRITPION
\fBajaxterm\fR is a web based terminal written in python and some AJAX
javascript for client side.
It can use almost any web browser and even works through firewalls.
.SH USAGE
\fBajaxterm.py\fR [options]
.SH OPTIONS
A summary of the options supported by \fBajaxterm\fR is included below.
\fB-h, --help\fR show this help message and exit
\fB-pPORT, --port=PORT\fR Set the TCP port (default: 8022)
\fB-cCMD, --command=CMD\fR set the command (default: /bin/login or ssh localhost)
\fB-l, --log\fR log requests to stderr (default: quiet mode)
.SH AUTHOR
Antony Lesuisse <al@udev.org>
This manual page was written for the Debian system by
Julien Valroff <julien@kirya.net> (but may be used by others).
.SH "REPORTING BUGS"
Report any bugs to the author: Antony Lesuisse <al@udev.org>
.SH COPYRIGHT
Copyright Antony Lesuisse <al@udev.org>
.SH SEE ALSO
- \fBajaxterm\fR wiki page: http://antony.lesuisse.org/qweb/trac/wiki/AjaxTerm
.br
- \fBajaxterm\fR forum: http://antony.lesuisse.org/qweb/forum/viewforum.php?id=2

View File

@ -0,0 +1,64 @@
pre.stat {
margin: 0px;
padding: 4px;
display: block;
font-family: monospace;
white-space: pre;
background-color: black;
border-top: 1px solid black;
color: white;
}
pre.stat span {
padding: 0px;
}
pre.stat .on {
background-color: #080;
font-weight: bold;
color: white;
cursor: pointer;
}
pre.stat .off {
background-color: #888;
font-weight: bold;
color: white;
cursor: pointer;
}
pre.term {
margin: 0px;
padding: 4px;
display: block;
font-family: monospace;
white-space: pre;
background-color: black;
border-top: 1px solid white;
color: #eee;
}
pre.term span.f0 { color: #000; }
pre.term span.f1 { color: #b00; }
pre.term span.f2 { color: #0b0; }
pre.term span.f3 { color: #bb0; }
pre.term span.f4 { color: #00b; }
pre.term span.f5 { color: #b0b; }
pre.term span.f6 { color: #0bb; }
pre.term span.f7 { color: #bbb; }
pre.term span.f8 { color: #666; }
pre.term span.f9 { color: #f00; }
pre.term span.f10 { color: #0f0; }
pre.term span.f11 { color: #ff0; }
pre.term span.f12 { color: #00f; }
pre.term span.f13 { color: #f0f; }
pre.term span.f14 { color: #0ff; }
pre.term span.f15 { color: #fff; }
pre.term span.b0 { background-color: #000; }
pre.term span.b1 { background-color: #b00; }
pre.term span.b2 { background-color: #0b0; }
pre.term span.b3 { background-color: #bb0; }
pre.term span.b4 { background-color: #00b; }
pre.term span.b5 { background-color: #b0b; }
pre.term span.b6 { background-color: #0bb; }
pre.term span.b7 { background-color: #bbb; }
body { background-color: #888; }
#term {
float: left;
}

View File

@ -0,0 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Ajaxterm</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" type="text/css" href="ajaxterm.css"/>
<script type="text/javascript" src="sarissa.js"></script>
<script type="text/javascript" src="sarissa_dhtml.js"></script>
<script type="text/javascript" src="ajaxterm.js"></script>
<script type="text/javascript">
/*
ajaxterm.py creates a random session_id to demultiplex multiple connections,
and to add a layer of security - in its shipping form, ajaxterm accepted any session_id
and was susceptible to an easy exploit
*/
SESSION_ID = '$session_id';
window.onload=function() {
t=ajaxterm.Terminal("term",80,25);
};
</script>
</head>
<body>
<div id="term"></div>
</body>
</html>

279
tools/ajaxterm/ajaxterm.js Normal file
View File

@ -0,0 +1,279 @@
ajaxterm={};
ajaxterm.Terminal_ctor=function(id,width,height) {
var ie=0;
if(window.ActiveXObject)
ie=1;
var sid=""+SESSION_ID;
var query0="s="+sid+"&w="+width+"&h="+height;
var query1=query0+"&c=1&k=";
var buf="";
var timeout;
var error_timeout;
var keybuf=[];
var sending=0;
var rmax=1;
var div=document.getElementById(id);
var dstat=document.createElement('pre');
var sled=document.createElement('span');
var opt_get=document.createElement('a');
var opt_color=document.createElement('a');
var opt_paste=document.createElement('a');
var sdebug=document.createElement('span');
var dterm=document.createElement('div');
function debug(s) {
sdebug.innerHTML=s;
}
function error() {
sled.className='off';
debug("Connection lost timeout ts:"+((new Date).getTime()));
}
function opt_add(opt,name) {
opt.className='off';
opt.innerHTML=' '+name+' ';
dstat.appendChild(opt);
dstat.appendChild(document.createTextNode(' '));
}
function do_get(event) {
opt_get.className=(opt_get.className=='off')?'on':'off';
debug('GET '+opt_get.className);
}
function do_color(event) {
var o=opt_color.className=(opt_color.className=='off')?'on':'off';
if(o=='on')
query1=query0+"&c=1&k=";
else
query1=query0+"&k=";
debug('Color '+opt_color.className);
}
function mozilla_clipboard() {
// mozilla sucks
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (err) {
debug('Access denied, <a href="http://kb.mozillazine.org/Granting_JavaScript_access_to_the_clipboard" target="_blank">more info</a>');
return undefined;
}
var clip = Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard);
var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
if (!clip || !trans) {
return undefined;
}
trans.addDataFlavor("text/unicode");
clip.getData(trans,clip.kGlobalClipboard);
var str=new Object();
var strLength=new Object();
try {
trans.getTransferData("text/unicode",str,strLength);
} catch(err) {
return "";
}
if (str) {
str=str.value.QueryInterface(Components.interfaces.nsISupportsString);
}
if (str) {
return str.data.substring(0,strLength.value / 2);
} else {
return "";
}
}
function do_paste(event) {
var p=undefined;
if (window.clipboardData) {
p=window.clipboardData.getData("Text");
} else if(window.netscape) {
p=mozilla_clipboard();
}
if (p) {
debug('Pasted');
queue(encodeURIComponent(p));
} else {
}
}
function update() {
// debug("ts: "+((new Date).getTime())+" rmax:"+rmax);
if(sending==0) {
sending=1;
sled.className='on';
var r=new XMLHttpRequest();
var send="";
while(keybuf.length>0) {
send+=keybuf.pop();
}
var query=query1+send;
if(opt_get.className=='on') {
r.open("GET","u?"+query,true);
if(ie) {
r.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
}
} else {
r.open("POST","u",true);
}
r.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
r.onreadystatechange = function () {
// debug("xhr:"+((new Date).getTime())+" state:"+r.readyState+" status:"+r.status+" statusText:"+r.statusText);
if (r.readyState==4) {
if(r.status==200) {
window.clearTimeout(error_timeout);
de=r.responseXML.documentElement;
if(de.tagName=="pre") {
if(ie) {
Sarissa.updateContentFromNode(de, dterm);
} else {
Sarissa.updateContentFromNode(de, dterm);
// old=div.firstChild;
// div.replaceChild(de,old);
}
rmax=100;
} else {
rmax*=2;
if(rmax>2000)
rmax=2000;
}
sending=0;
sled.className='off';
timeout=window.setTimeout(update,rmax);
} else {
debug("Connection error status:"+r.status);
}
}
}
error_timeout=window.setTimeout(error,5000);
if(opt_get.className=='on') {
r.send(null);
} else {
r.send(query);
}
}
}
function queue(s) {
keybuf.unshift(s);
if(sending==0) {
window.clearTimeout(timeout);
timeout=window.setTimeout(update,1);
}
}
function keypress(ev) {
if (!ev) var ev=window.event;
// s="kp keyCode="+ev.keyCode+" which="+ev.which+" shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey;
// debug(s);
// return false;
// else { if (!ev.ctrlKey || ev.keyCode==17) { return; }
var kc;
var k="";
if (ev.keyCode)
kc=ev.keyCode;
if (ev.which)
kc=ev.which;
if (ev.altKey) {
if (kc>=65 && kc<=90)
kc+=32;
if (kc>=97 && kc<=122) {
k=String.fromCharCode(27)+String.fromCharCode(kc);
}
} else if (ev.ctrlKey) {
if (kc>=65 && kc<=90) k=String.fromCharCode(kc-64); // Ctrl-A..Z
else if (kc>=97 && kc<=122) k=String.fromCharCode(kc-96); // Ctrl-A..Z
else if (kc==54) k=String.fromCharCode(30); // Ctrl-^
else if (kc==109) k=String.fromCharCode(31); // Ctrl-_
else if (kc==219) k=String.fromCharCode(27); // Ctrl-[
else if (kc==220) k=String.fromCharCode(28); // Ctrl-\
else if (kc==221) k=String.fromCharCode(29); // Ctrl-]
else if (kc==219) k=String.fromCharCode(29); // Ctrl-]
else if (kc==219) k=String.fromCharCode(0); // Ctrl-@
} else if (ev.which==0) {
if (kc==9) k=String.fromCharCode(9); // Tab
else if (kc==8) k=String.fromCharCode(127); // Backspace
else if (kc==27) k=String.fromCharCode(27); // Escape
else {
if (kc==33) k="[5~"; // PgUp
else if (kc==34) k="[6~"; // PgDn
else if (kc==35) k="[4~"; // End
else if (kc==36) k="[1~"; // Home
else if (kc==37) k="[D"; // Left
else if (kc==38) k="[A"; // Up
else if (kc==39) k="[C"; // Right
else if (kc==40) k="[B"; // Down
else if (kc==45) k="[2~"; // Ins
else if (kc==46) k="[3~"; // Del
else if (kc==112) k="[[A"; // F1
else if (kc==113) k="[[B"; // F2
else if (kc==114) k="[[C"; // F3
else if (kc==115) k="[[D"; // F4
else if (kc==116) k="[[E"; // F5
else if (kc==117) k="[17~"; // F6
else if (kc==118) k="[18~"; // F7
else if (kc==119) k="[19~"; // F8
else if (kc==120) k="[20~"; // F9
else if (kc==121) k="[21~"; // F10
else if (kc==122) k="[23~"; // F11
else if (kc==123) k="[24~"; // F12
if (k.length) {
k=String.fromCharCode(27)+k;
}
}
} else {
if (kc==8)
k=String.fromCharCode(127); // Backspace
else
k=String.fromCharCode(kc);
}
if(k.length) {
// queue(encodeURIComponent(k));
if(k=="+") {
queue("%2B");
} else {
queue(escape(k));
}
}
ev.cancelBubble=true;
if (ev.stopPropagation) ev.stopPropagation();
if (ev.preventDefault) ev.preventDefault();
return false;
}
function keydown(ev) {
if (!ev) var ev=window.event;
if (ie) {
// s="kd keyCode="+ev.keyCode+" which="+ev.which+" shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey;
// debug(s);
o={9:1,8:1,27:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1,45:1,46:1,112:1,
113:1,114:1,115:1,116:1,117:1,118:1,119:1,120:1,121:1,122:1,123:1};
if (o[ev.keyCode] || ev.ctrlKey || ev.altKey) {
ev.which=0;
return keypress(ev);
}
}
}
function init() {
sled.appendChild(document.createTextNode('\xb7'));
sled.className='off';
dstat.appendChild(sled);
dstat.appendChild(document.createTextNode(' '));
opt_add(opt_color,'Colors');
opt_color.className='on';
opt_add(opt_get,'GET');
opt_add(opt_paste,'Paste');
dstat.appendChild(sdebug);
dstat.className='stat';
div.appendChild(dstat);
div.appendChild(dterm);
if(opt_color.addEventListener) {
opt_get.addEventListener('click',do_get,true);
opt_color.addEventListener('click',do_color,true);
opt_paste.addEventListener('click',do_paste,true);
} else {
opt_get.attachEvent("onclick", do_get);
opt_color.attachEvent("onclick", do_color);
opt_paste.attachEvent("onclick", do_paste);
}
document.onkeypress=keypress;
document.onkeydown=keydown;
timeout=window.setTimeout(update,100);
}
init();
}
ajaxterm.Terminal=function(id,width,height) {
return new this.Terminal_ctor(id,width,height);
}

586
tools/ajaxterm/ajaxterm.py Executable file
View File

@ -0,0 +1,586 @@
#!/usr/bin/env python
""" Ajaxterm """
import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd
os.chdir(os.path.normpath(os.path.dirname(__file__)))
# Optional: Add QWeb in sys path
sys.path[0:0]=glob.glob('../../python')
import qweb
import string, subprocess, uuid
global g_server
TIMEOUT=300
class Terminal:
def __init__(self,width=80,height=24):
self.width=width
self.height=height
self.init()
self.reset()
def init(self):
self.esc_seq={
"\x00": None,
"\x05": self.esc_da,
"\x07": None,
"\x08": self.esc_0x08,
"\x09": self.esc_0x09,
"\x0a": self.esc_0x0a,
"\x0b": self.esc_0x0a,
"\x0c": self.esc_0x0a,
"\x0d": self.esc_0x0d,
"\x0e": None,
"\x0f": None,
"\x1b#8": None,
"\x1b=": None,
"\x1b>": None,
"\x1b(0": None,
"\x1b(A": None,
"\x1b(B": None,
"\x1b[c": self.esc_da,
"\x1b[0c": self.esc_da,
"\x1b]R": None,
"\x1b7": self.esc_save,
"\x1b8": self.esc_restore,
"\x1bD": None,
"\x1bE": None,
"\x1bH": None,
"\x1bM": self.esc_ri,
"\x1bN": None,
"\x1bO": None,
"\x1bZ": self.esc_da,
"\x1ba": None,
"\x1bc": self.reset,
"\x1bn": None,
"\x1bo": None,
}
for k,v in self.esc_seq.items():
if v==None:
self.esc_seq[k]=self.esc_ignore
# regex
d={
r'\[\??([0-9;]*)([@ABCDEFGHJKLMPXacdefghlmnqrstu`])' : self.csi_dispatch,
r'\]([^\x07]+)\x07' : self.esc_ignore,
}
self.esc_re=[]
for k,v in d.items():
self.esc_re.append((re.compile('\x1b'+k),v))
# define csi sequences
self.csi_seq={
'@': (self.csi_at,[1]),
'`': (self.csi_G,[1]),
'J': (self.csi_J,[0]),
'K': (self.csi_K,[0]),
}
for i in [i[4] for i in dir(self) if i.startswith('csi_') and len(i)==5]:
if not self.csi_seq.has_key(i):
self.csi_seq[i]=(getattr(self,'csi_'+i),[1])
# Init 0-256 to latin1 and html translation table
self.trl1=""
for i in range(256):
if i<32:
self.trl1+=" "
elif i<127 or i>160:
self.trl1+=chr(i)
else:
self.trl1+="?"
self.trhtml=""
for i in range(256):
if i==0x0a or (i>32 and i<127) or i>160:
self.trhtml+=chr(i)
elif i<=32:
self.trhtml+="\xa0"
else:
self.trhtml+="?"
def reset(self,s=""):
self.scr=array.array('i',[0x000700]*(self.width*self.height))
self.st=0
self.sb=self.height-1
self.cx_bak=self.cx=0
self.cy_bak=self.cy=0
self.cl=0
self.sgr=0x000700
self.buf=""
self.outbuf=""
self.last_html=""
def peek(self,y1,x1,y2,x2):
return self.scr[self.width*y1+x1:self.width*y2+x2]
def poke(self,y,x,s):
pos=self.width*y+x
self.scr[pos:pos+len(s)]=s
def zero(self,y1,x1,y2,x2):
w=self.width*(y2-y1)+x2-x1+1
z=array.array('i',[0x000700]*w)
self.scr[self.width*y1+x1:self.width*y2+x2+1]=z
def scroll_up(self,y1,y2):
self.poke(y1,0,self.peek(y1+1,0,y2,self.width))
self.zero(y2,0,y2,self.width-1)
def scroll_down(self,y1,y2):
self.poke(y1+1,0,self.peek(y1,0,y2-1,self.width))
self.zero(y1,0,y1,self.width-1)
def scroll_right(self,y,x):
self.poke(y,x+1,self.peek(y,x,y,self.width))
self.zero(y,x,y,x)
def cursor_down(self):
if self.cy>=self.st and self.cy<=self.sb:
self.cl=0
q,r=divmod(self.cy+1,self.sb+1)
if q:
self.scroll_up(self.st,self.sb)
self.cy=self.sb
else:
self.cy=r
def cursor_right(self):
q,r=divmod(self.cx+1,self.width)
if q:
self.cl=1
else:
self.cx=r
def echo(self,c):
if self.cl:
self.cursor_down()
self.cx=0
self.scr[(self.cy*self.width)+self.cx]=self.sgr|ord(c)
self.cursor_right()
def esc_0x08(self,s):
self.cx=max(0,self.cx-1)
def esc_0x09(self,s):
x=self.cx+8
q,r=divmod(x,8)
self.cx=(q*8)%self.width
def esc_0x0a(self,s):
self.cursor_down()
def esc_0x0d(self,s):
self.cl=0
self.cx=0
def esc_save(self,s):
self.cx_bak=self.cx
self.cy_bak=self.cy
def esc_restore(self,s):
self.cx=self.cx_bak
self.cy=self.cy_bak
self.cl=0
def esc_da(self,s):
self.outbuf="\x1b[?6c"
def esc_ri(self,s):
self.cy=max(self.st,self.cy-1)
if self.cy==self.st:
self.scroll_down(self.st,self.sb)
def esc_ignore(self,*s):
pass
# print "term:ignore: %s"%repr(s)
def csi_dispatch(self,seq,mo):
# CSI sequences
s=mo.group(1)
c=mo.group(2)
f=self.csi_seq.get(c,None)
if f:
try:
l=[min(int(i),1024) for i in s.split(';') if len(i)<4]
except ValueError:
l=[]
if len(l)==0:
l=f[1]
f[0](l)
# else:
# print 'csi ignore',c,l
def csi_at(self,l):
for i in range(l[0]):
self.scroll_right(self.cy,self.cx)
def csi_A(self,l):
self.cy=max(self.st,self.cy-l[0])
def csi_B(self,l):
self.cy=min(self.sb,self.cy+l[0])
def csi_C(self,l):
self.cx=min(self.width-1,self.cx+l[0])
self.cl=0
def csi_D(self,l):
self.cx=max(0,self.cx-l[0])
self.cl=0
def csi_E(self,l):
self.csi_B(l)
self.cx=0
self.cl=0
def csi_F(self,l):
self.csi_A(l)
self.cx=0
self.cl=0
def csi_G(self,l):
self.cx=min(self.width,l[0])-1
def csi_H(self,l):
if len(l)<2: l=[1,1]
self.cx=min(self.width,l[1])-1
self.cy=min(self.height,l[0])-1
self.cl=0
def csi_J(self,l):
if l[0]==0:
self.zero(self.cy,self.cx,self.height-1,self.width-1)
elif l[0]==1:
self.zero(0,0,self.cy,self.cx)
elif l[0]==2:
self.zero(0,0,self.height-1,self.width-1)
def csi_K(self,l):
if l[0]==0:
self.zero(self.cy,self.cx,self.cy,self.width-1)
elif l[0]==1:
self.zero(self.cy,0,self.cy,self.cx)
elif l[0]==2:
self.zero(self.cy,0,self.cy,self.width-1)
def csi_L(self,l):
for i in range(l[0]):
if self.cy<self.sb:
self.scroll_down(self.cy,self.sb)
def csi_M(self,l):
if self.cy>=self.st and self.cy<=self.sb:
for i in range(l[0]):
self.scroll_up(self.cy,self.sb)
def csi_P(self,l):
w,cx,cy=self.width,self.cx,self.cy
end=self.peek(cy,cx,cy,w)
self.csi_K([0])
self.poke(cy,cx,end[l[0]:])
def csi_X(self,l):
self.zero(self.cy,self.cx,self.cy,self.cx+l[0])
def csi_a(self,l):
self.csi_C(l)
def csi_c(self,l):
#'\x1b[?0c' 0-8 cursor size
pass
def csi_d(self,l):
self.cy=min(self.height,l[0])-1
def csi_e(self,l):
self.csi_B(l)
def csi_f(self,l):
self.csi_H(l)
def csi_h(self,l):
if l[0]==4:
pass
# print "insert on"
def csi_l(self,l):
if l[0]==4:
pass
# print "insert off"
def csi_m(self,l):
for i in l:
if i==0 or i==39 or i==49 or i==27:
self.sgr=0x000700
elif i==1:
self.sgr=(self.sgr|0x000800)
elif i==7:
self.sgr=0x070000
elif i>=30 and i<=37:
c=i-30
self.sgr=(self.sgr&0xff08ff)|(c<<8)
elif i>=40 and i<=47:
c=i-40
self.sgr=(self.sgr&0x00ffff)|(c<<16)
# else:
# print "CSI sgr ignore",l,i
# print 'sgr: %r %x'%(l,self.sgr)
def csi_r(self,l):
if len(l)<2: l=[0,self.height]
self.st=min(self.height-1,l[0]-1)
self.sb=min(self.height-1,l[1]-1)
self.sb=max(self.st,self.sb)
def csi_s(self,l):
self.esc_save(0)
def csi_u(self,l):
self.esc_restore(0)
def escape(self):
e=self.buf
if len(e)>32:
# print "error %r"%e
self.buf=""
elif e in self.esc_seq:
self.esc_seq[e](e)
self.buf=""
else:
for r,f in self.esc_re:
mo=r.match(e)
if mo:
f(e,mo)
self.buf=""
break
# if self.buf=='': print "ESC %r\n"%e
def write(self,s):
for i in s:
if len(self.buf) or (i in self.esc_seq):
self.buf+=i
self.escape()
elif i == '\x1b':
self.buf+=i
else:
self.echo(i)
def read(self):
b=self.outbuf
self.outbuf=""
return b
def dump(self):
r=''
for i in self.scr:
r+=chr(i&255)
return r
def dumplatin1(self):
return self.dump().translate(self.trl1)
def dumphtml(self,color=1):
h=self.height
w=self.width
r=""
span=""
span_bg,span_fg=-1,-1
for i in range(h*w):
q,c=divmod(self.scr[i],256)
if color:
bg,fg=divmod(q,256)
else:
bg,fg=0,7
if i==self.cy*w+self.cx:
bg,fg=1,7
if (bg!=span_bg or fg!=span_fg or i==h*w-1):
if len(span):
r+='<span class="f%d b%d">%s</span>'%(span_fg,span_bg,cgi.escape(span.translate(self.trhtml)))
span=""
span_bg,span_fg=bg,fg
span+=chr(c)
if i%w==w-1:
span+='\n'
r='<?xml version="1.0" encoding="ISO-8859-1"?><pre class="term">%s</pre>'%r
if self.last_html==r:
return '<?xml version="1.0"?><idem></idem>'
else:
self.last_html=r
# print self
return r
def __repr__(self):
d=self.dumplatin1()
r=""
for i in range(self.height):
r+="|%s|\n"%d[self.width*i:self.width*(i+1)]
return r
class SynchronizedMethod:
def __init__(self,lock,orig):
self.lock=lock
self.orig=orig
def __call__(self,*l):
self.lock.acquire()
r=self.orig(*l)
self.lock.release()
return r
class Multiplex:
def __init__(self,cmd=None):
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
self.cmd=cmd
self.proc={}
self.lock=threading.RLock()
self.thread=threading.Thread(target=self.loop)
self.alive=1
self.lastActivity=time.time()
# synchronize methods
for name in ['create','fds','proc_read','proc_write','dump','die','run']:
orig=getattr(self,name)
setattr(self,name,SynchronizedMethod(self.lock,orig))
self.thread.start()
def create(self,w=80,h=25):
pid,fd=pty.fork()
if pid==0:
try:
fdl=[int(i) for i in os.listdir('/proc/self/fd')]
except OSError:
fdl=range(256)
for i in [i for i in fdl if i>2]:
try:
os.close(i)
except OSError:
pass
if self.cmd:
cmd=['/bin/sh','-c',self.cmd]
elif os.getuid()==0:
cmd=['/bin/login']
else:
sys.stdout.write("Login: ")
login=sys.stdin.readline().strip()
if re.match('^[0-9A-Za-z-_. ]+$',login):
cmd=['ssh']
cmd+=['-oPreferredAuthentications=keyboard-interactive,password']
cmd+=['-oNoHostAuthenticationForLocalhost=yes']
cmd+=['-oLogLevel=FATAL']
cmd+=['-F/dev/null','-l',login,'localhost']
else:
os._exit(0)
env={}
env["COLUMNS"]=str(w)
env["LINES"]=str(h)
env["TERM"]="linux"
env["PATH"]=os.environ['PATH']
os.execvpe(cmd[0],cmd,env)
else:
fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
# python bug http://python.org/sf/1112949 on amd64
fcntl.ioctl(fd, struct.unpack('i',struct.pack('I',termios.TIOCSWINSZ))[0], struct.pack("HHHH",h,w,0,0))
self.proc[fd]={'pid':pid,'term':Terminal(w,h),'buf':'','time':time.time()}
return fd
def die(self):
self.alive=0
def run(self):
return self.alive
def fds(self):
return self.proc.keys()
def proc_kill(self,fd):
if fd in self.proc:
self.proc[fd]['time']=0
t=time.time()
for i in self.proc.keys():
t0=self.proc[i]['time']
if (t-t0)>TIMEOUT:
try:
os.close(i)
os.kill(self.proc[i]['pid'],signal.SIGTERM)
except (IOError,OSError):
pass
del self.proc[i]
def proc_read(self,fd):
try:
t=self.proc[fd]['term']
t.write(os.read(fd,65536))
reply=t.read()
if reply:
os.write(fd,reply)
self.proc[fd]['time']=time.time()
except (KeyError,IOError,OSError):
self.proc_kill(fd)
def proc_write(self,fd,s):
try:
os.write(fd,s)
except (IOError,OSError):
self.proc_kill(fd)
def dump(self,fd,color=1):
try:
return self.proc[fd]['term'].dumphtml(color)
except KeyError:
return False
def loop(self):
while self.run():
fds=self.fds()
i,o,e=select.select(fds, [], [], 1.0)
if time.time() - self.lastActivity > TIMEOUT:
global g_server
g_server.shutdown()
for fd in i:
self.proc_read(fd)
if len(i):
time.sleep(0.002)
for i in self.proc.keys():
try:
os.close(i)
os.kill(self.proc[i]['pid'],signal.SIGTERM)
except (IOError,OSError):
pass
class AjaxTerm:
def __init__(self,cmd=None,index_file='ajaxterm.html',token=None):
self.files={}
self.token=token
for i in ['css','html','js']:
for j in glob.glob('*.%s'%i):
self.files[j]=file(j).read()
self.files['index']=file(index_file).read()
self.mime = mimetypes.types_map.copy()
self.mime['.html']= 'text/html; charset=UTF-8'
self.multi = Multiplex(cmd)
self.session = {}
def __call__(self, environ, start_response):
req = qweb.QWebRequest(environ, start_response,session=None)
if req.PATH_INFO.endswith('/u'):
s=req.REQUEST["s"]
k=req.REQUEST["k"]
c=req.REQUEST["c"]
w=req.REQUEST.int("w")
h=req.REQUEST.int("h")
if s in self.session:
term=self.session[s]
else:
raise Exception('Not Authorized')
# The original code below was insecure, because it allowed unauthorized sessions to be created
# if not (w>2 and w<256 and h>2 and h<100):
# w,h=80,25
# term=self.session[s]=self.multi.create(w,h)
if k:
self.multi.proc_write(term,k)
time.sleep(0.002)
self.multi.lastActivity = time.time();
dump=self.multi.dump(term,c)
req.response_headers['Content-Type']='text/xml'
if isinstance(dump,str):
req.write(dump)
req.response_gzencode=1
else:
del self.session[s]
req.write('<?xml version="1.0"?><idem></idem>')
# print "sessions %r"%self.session
else:
n=os.path.basename(req.PATH_INFO)
if n in self.files:
req.response_headers['Content-Type'] = self.mime.get(os.path.splitext(n)[1].lower(), 'application/octet-stream')
req.write(self.files[n])
elif req.REQUEST['token'] == self.token:
req.response_headers['Content-Type'] = 'text/html; charset=UTF-8'
session_id = str(uuid.uuid4())
req.write(string.Template(self.files['index']).substitute(session_id=session_id))
term=self.session[session_id]=self.multi.create(80,25)
else:
raise Exception("Not Authorized")
return req
def main():
parser = optparse.OptionParser()
parser.add_option("-p", "--port", dest="port", default="8022", help="Set the TCP port (default: 8022)")
parser.add_option("-c", "--command", dest="cmd", default=None,help="set the command (default: /bin/login or ssh 0.0.0.0)")
parser.add_option("-l", "--log", action="store_true", dest="log",default=0,help="log requests to stderr (default: quiet mode)")
parser.add_option("-d", "--daemon", action="store_true", dest="daemon", default=0, help="run as daemon in the background")
parser.add_option("-P", "--pidfile",dest="pidfile",default="/var/run/ajaxterm.pid",help="set the pidfile (default: /var/run/ajaxterm.pid)")
parser.add_option("-i", "--index", dest="index_file", default="ajaxterm.html",help="default index file (default: ajaxterm.html)")
parser.add_option("-u", "--uid", dest="uid", help="Set the daemon's user id")
parser.add_option("-t", "--token", dest="token", help="Set authorization token")
(o, a) = parser.parse_args()
if o.daemon:
pid=os.fork()
if pid == 0:
#os.setsid() ?
os.setpgrp()
nullin = file('/dev/null', 'r')
nullout = file('/dev/null', 'w')
os.dup2(nullin.fileno(), sys.stdin.fileno())
os.dup2(nullout.fileno(), sys.stdout.fileno())
os.dup2(nullout.fileno(), sys.stderr.fileno())
if os.getuid()==0 and o.uid:
try:
os.setuid(int(o.uid))
except:
os.setuid(pwd.getpwnam(o.uid).pw_uid)
else:
try:
file(o.pidfile,'w+').write(str(pid)+'\n')
except:
pass
print 'AjaxTerm at http://0.0.0.0:%s/ pid: %d' % (o.port,pid)
sys.exit(0)
else:
print 'AjaxTerm at http://0.0.0.0:%s/' % o.port
at=AjaxTerm(o.cmd,o.index_file,o.token)
# f=lambda:os.system('firefox http://localhost:%s/&'%o.port)
# qweb.qweb_wsgi_autorun(at,ip='localhost',port=int(o.port),threaded=0,log=o.log,callback_ready=None)
try:
global g_server
g_server = qweb.QWebWSGIServer(at,ip='0.0.0.0',port=int(o.port),threaded=0,log=o.log)
g_server.serve_forever()
except KeyboardInterrupt,e:
sys.excepthook(*sys.exc_info())
at.multi.die()
if __name__ == '__main__':
main()

32
tools/ajaxterm/configure vendored Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env python
import optparse,os
parser = optparse.OptionParser()
parser.add_option("", "--prefix", dest="prefix",default="/usr/local",help="installation prefix (default: /usr/local)")
parser.add_option("", "--confdir", dest="confdir", default="/etc",help="configuration files directory prefix (default: /etc)")
parser.add_option("", "--port", dest="port", default="8022", help="set the listening TCP port (default: 8022)")
parser.add_option("", "--command", dest="cmd", default=None,help="set the command (default: /bin/login or ssh localhost)")
(o, a) = parser.parse_args()
print "Configuring prefix=",o.prefix," port=",o.port
etc=o.confdir
port=o.port
cmd=o.cmd
bin=os.path.join(o.prefix,"bin")
lib=os.path.join(o.prefix,"share/ajaxterm")
man=os.path.join(o.prefix,"share/man/man1")
file("ajaxterm.bin","w").write(file("configure.ajaxterm.bin").read()%locals())
file("Makefile","w").write(file("configure.makefile").read()%locals())
if os.path.isfile("/etc/gentoo-release"):
file("ajaxterm.initd","w").write(file("configure.initd.gentoo").read()%locals())
elif os.path.isfile("/etc/fedora-release") or os.path.isfile("/etc/redhat-release"):
file("ajaxterm.initd","w").write(file("configure.initd.redhat").read()%locals())
else:
file("ajaxterm.initd","w").write(file("configure.initd.debian").read()%locals())
os.system("chmod a+x ajaxterm.bin")
os.system("chmod a+x ajaxterm.initd")

View File

@ -0,0 +1,2 @@
#!/bin/sh
PYTHONPATH=%(lib)s exec %(lib)s/ajaxterm.py $@

View File

@ -0,0 +1,33 @@
#!/bin/sh
PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
DAEMON=%(bin)s/ajaxterm
PORT=%(port)s
PIDFILE=/var/run/ajaxterm.pid
[ -x "$DAEMON" ] || exit 0
#. /lib/lsb/init-functions
case "$1" in
start)
echo "Starting ajaxterm on port $PORT"
start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- --daemon --port=$PORT --uid=nobody || return 2
;;
stop)
echo "Stopping ajaxterm"
start-stop-daemon --stop --pidfile $PIDFILE
rm -f $PIDFILE
;;
restart|force-reload)
$0 stop
sleep 1
$0 start
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
exit 3
;;
esac
:

View File

@ -0,0 +1,27 @@
#!/sbin/runscript
# AjaxTerm Gentoo script, 08 May 2006 Mark Gillespie
DAEMON=%(bin)s/ajaxterm
PORT=%(port)s
PIDFILE=/var/run/ajaxterm.pid
depend()
{
need net
}
start()
{
ebegin "Starting AjaxTerm on port $PORT"
start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- --daemon --port=$PORT --uid=nobody
eend $?
}
stop()
{
ebegin "Stopping AjaxTerm"
start-stop-daemon --stop --pidfile $PIDFILE
rm -f $PIDFILE
eend $?
}

View File

@ -0,0 +1,75 @@
#
# ajaxterm Startup script for ajaxterm
#
# chkconfig: - 99 99
# description: Ajaxterm is a yadda yadda yadda
# processname: ajaxterm
# pidfile: /var/run/ajaxterm.pid
# version: 1.0 Kevin Reichhart - ajaxterminit at lastname dot org
# Source function library.
. /etc/rc.d/init.d/functions
if [ -f /etc/sysconfig/ajaxterm ]; then
. /etc/sysconfig/ajaxterm
fi
ajaxterm=/usr/local/bin/ajaxterm
prog=ajaxterm
pidfile=${PIDFILE-/var/run/ajaxterm.pid}
lockfile=${LOCKFILE-/var/lock/subsys/ajaxterm}
port=${PORT-8022}
user=${xUSER-nobody}
RETVAL=0
start() {
echo -n $"Starting $prog: "
daemon $ajaxterm --daemon --port=$port --uid=$user $OPTIONS
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch ${lockfile}
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
killproc $ajaxterm
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
}
reload() {
echo -n $"Reloading $prog: "
killproc $ajaxterm -HUP
RETVAL=$?
echo
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status python ajaxterm
RETVAL=$?
;;
restart)
stop
start
;;
condrestart)
if [ -f ${pidfile} ] ; then
stop
start
fi
;;
*)
echo $"Usage: $prog {start|stop|restart|condrestart}"
exit 1
esac
exit $RETVAL

View File

@ -0,0 +1,20 @@
build:
true
install:
install -d "%(bin)s"
install -d "%(lib)s"
install ajaxterm.bin "%(bin)s/ajaxterm"
install ajaxterm.initd "%(etc)s/init.d/ajaxterm"
install -m 644 ajaxterm.css ajaxterm.html ajaxterm.js qweb.py sarissa.js sarissa_dhtml.js "%(lib)s"
install -m 755 ajaxterm.py "%(lib)s"
gzip --best -c ajaxterm.1 > ajaxterm.1.gz
install -d "%(man)s"
install ajaxterm.1.gz "%(man)s"
clean:
rm ajaxterm.bin
rm ajaxterm.initd
rm ajaxterm.1.gz
rm Makefile

1356
tools/ajaxterm/qweb.py Normal file

File diff suppressed because it is too large Load Diff

647
tools/ajaxterm/sarissa.js Normal file
View File

@ -0,0 +1,647 @@
/**
* ====================================================================
* About
* ====================================================================
* Sarissa is an ECMAScript library acting as a cross-browser wrapper for native XML APIs.
* The library supports Gecko based browsers like Mozilla and Firefox,
* Internet Explorer (5.5+ with MSXML3.0+), Konqueror, Safari and a little of Opera
* @version 0.9.6.1
* @author: Manos Batsis, mailto: mbatsis at users full stop sourceforge full stop net
* ====================================================================
* Licence
* ====================================================================
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* the GNU Lesser General Public License version 2.1 as published by
* the Free Software Foundation (your choice between the two).
*
* 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 or GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* or GNU Lesser General Public License along with this program; if not,
* write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* or visit http://www.gnu.org
*
*/
/**
* <p>Sarissa is a utility class. Provides "static" methods for DOMDocument and
* XMLHTTP objects, DOM Node serializatrion to XML strings and other goodies.</p>
* @constructor
*/
function Sarissa(){};
/** @private */
Sarissa.PARSED_OK = "Document contains no parsing errors";
/**
* Tells you whether transformNode and transformNodeToObject are available. This functionality
* is contained in sarissa_ieemu_xslt.js and is deprecated. If you want to control XSLT transformations
* use the XSLTProcessor
* @deprecated
* @type boolean
*/
Sarissa.IS_ENABLED_TRANSFORM_NODE = false;
/**
* tells you whether XMLHttpRequest (or equivalent) is available
* @type boolean
*/
Sarissa.IS_ENABLED_XMLHTTP = false;
/**
* tells you whether selectNodes/selectSingleNode is available
* @type boolean
*/
Sarissa.IS_ENABLED_SELECT_NODES = false;
var _sarissa_iNsCounter = 0;
var _SARISSA_IEPREFIX4XSLPARAM = "";
var _SARISSA_HAS_DOM_IMPLEMENTATION = document.implementation && true;
var _SARISSA_HAS_DOM_CREATE_DOCUMENT = _SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.createDocument;
var _SARISSA_HAS_DOM_FEATURE = _SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.hasFeature;
var _SARISSA_IS_MOZ = _SARISSA_HAS_DOM_CREATE_DOCUMENT && _SARISSA_HAS_DOM_FEATURE;
var _SARISSA_IS_SAFARI = (navigator.userAgent && navigator.vendor && (navigator.userAgent.toLowerCase().indexOf("applewebkit") != -1 || navigator.vendor.indexOf("Apple") != -1));
var _SARISSA_IS_IE = document.all && window.ActiveXObject && navigator.userAgent.toLowerCase().indexOf("msie") > -1 && navigator.userAgent.toLowerCase().indexOf("opera") == -1;
if(!window.Node || !window.Node.ELEMENT_NODE){
var Node = {ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12};
};
// IE initialization
if(_SARISSA_IS_IE){
// for XSLT parameter names, prefix needed by IE
_SARISSA_IEPREFIX4XSLPARAM = "xsl:";
// used to store the most recent ProgID available out of the above
var _SARISSA_DOM_PROGID = "";
var _SARISSA_XMLHTTP_PROGID = "";
/**
* Called when the Sarissa_xx.js file is parsed, to pick most recent
* ProgIDs for IE, then gets destroyed.
* @param idList an array of MSXML PROGIDs from which the most recent will be picked for a given object
* @param enabledList an array of arrays where each array has two items; the index of the PROGID for which a certain feature is enabled
*/
pickRecentProgID = function (idList, enabledList){
// found progID flag
var bFound = false;
for(var i=0; i < idList.length && !bFound; i++){
try{
var oDoc = new ActiveXObject(idList[i]);
o2Store = idList[i];
bFound = true;
for(var j=0;j<enabledList.length;j++)
if(i <= enabledList[j][1])
Sarissa["IS_ENABLED_"+enabledList[j][0]] = true;
}catch (objException){
// trap; try next progID
};
};
if (!bFound)
throw "Could not retreive a valid progID of Class: " + idList[idList.length-1]+". (original exception: "+e+")";
idList = null;
return o2Store;
};
// pick best available MSXML progIDs
_SARISSA_DOM_PROGID = pickRecentProgID(["Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"], [["SELECT_NODES", 2],["TRANSFORM_NODE", 2]]);
_SARISSA_XMLHTTP_PROGID = pickRecentProgID(["Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"], [["XMLHTTP", 4]]);
_SARISSA_THREADEDDOM_PROGID = pickRecentProgID(["Msxml2.FreeThreadedDOMDocument.5.0", "MSXML2.FreeThreadedDOMDocument.4.0", "MSXML2.FreeThreadedDOMDocument.3.0"]);
_SARISSA_XSLTEMPLATE_PROGID = pickRecentProgID(["Msxml2.XSLTemplate.5.0", "Msxml2.XSLTemplate.4.0", "MSXML2.XSLTemplate.3.0"], [["XSLTPROC", 2]]);
// we dont need this anymore
pickRecentProgID = null;
//============================================
// Factory methods (IE)
//============================================
// see non-IE version
Sarissa.getDomDocument = function(sUri, sName){
var oDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
// if a root tag name was provided, we need to load it in the DOM
// object
if (sName){
// if needed, create an artifical namespace prefix the way Moz
// does
if (sUri){
oDoc.loadXML("<a" + _sarissa_iNsCounter + ":" + sName + " xmlns:a" + _sarissa_iNsCounter + "=\"" + sUri + "\" />");
// don't use the same prefix again
++_sarissa_iNsCounter;
}
else
oDoc.loadXML("<" + sName + "/>");
};
return oDoc;
};
// see non-IE version
Sarissa.getParseErrorText = function (oDoc) {
var parseErrorText = Sarissa.PARSED_OK;
if(oDoc.parseError != 0){
parseErrorText = "XML Parsing Error: " + oDoc.parseError.reason +
"\nLocation: " + oDoc.parseError.url +
"\nLine Number " + oDoc.parseError.line + ", Column " +
oDoc.parseError.linepos +
":\n" + oDoc.parseError.srcText +
"\n";
for(var i = 0; i < oDoc.parseError.linepos;i++){
parseErrorText += "-";
};
parseErrorText += "^\n";
};
return parseErrorText;
};
// see non-IE version
Sarissa.setXpathNamespaces = function(oDoc, sNsSet) {
oDoc.setProperty("SelectionLanguage", "XPath");
oDoc.setProperty("SelectionNamespaces", sNsSet);
};
/**
* Basic implementation of Mozilla's XSLTProcessor for IE.
* Reuses the same XSLT stylesheet for multiple transforms
* @constructor
*/
XSLTProcessor = function(){
this.template = new ActiveXObject(_SARISSA_XSLTEMPLATE_PROGID);
this.processor = null;
};
/**
* Impoprts the given XSLT DOM and compiles it to a reusable transform
* @argument xslDoc The XSLT DOMDocument to import
*/
XSLTProcessor.prototype.importStylesheet = function(xslDoc){
// convert stylesheet to free threaded
var converted = new ActiveXObject(_SARISSA_THREADEDDOM_PROGID);
converted.loadXML(xslDoc.xml);
this.template.stylesheet = converted;
this.processor = this.template.createProcessor();
// (re)set default param values
this.paramsSet = new Array();
};
/**
* Transform the given XML DOM
* @argument sourceDoc The XML DOMDocument to transform
* @return The transformation result as a DOM Document
*/
XSLTProcessor.prototype.transformToDocument = function(sourceDoc){
this.processor.input = sourceDoc;
var outDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
this.processor.output = outDoc;
this.processor.transform();
return outDoc;
};
/**
* Set global XSLT parameter of the imported stylesheet
* @argument nsURI The parameter namespace URI
* @argument name The parameter base name
* @argument value The new parameter value
*/
XSLTProcessor.prototype.setParameter = function(nsURI, name, value){
/* nsURI is optional but cannot be null */
if(nsURI){
this.processor.addParameter(name, value, nsURI);
}else{
this.processor.addParameter(name, value);
};
/* update updated params for getParameter */
if(!this.paramsSet[""+nsURI]){
this.paramsSet[""+nsURI] = new Array();
};
this.paramsSet[""+nsURI][name] = value;
};
/**
* Gets a parameter if previously set by setParameter. Returns null
* otherwise
* @argument name The parameter base name
* @argument value The new parameter value
* @return The parameter value if reviously set by setParameter, null otherwise
*/
XSLTProcessor.prototype.getParameter = function(nsURI, name){
nsURI = nsURI || "";
if(nsURI in this.paramsSet && name in this.paramsSet[nsURI]){
return this.paramsSet[nsURI][name];
}else{
return null;
};
};
}
else{ /* end IE initialization, try to deal with real browsers now ;-) */
if(_SARISSA_HAS_DOM_CREATE_DOCUMENT){
/**
* <p>Ensures the document was loaded correctly, otherwise sets the
* parseError to -1 to indicate something went wrong. Internal use</p>
* @private
*/
Sarissa.__handleLoad__ = function(oDoc){
if (!oDoc.documentElement || oDoc.documentElement.tagName == "parsererror")
oDoc.parseError = -1;
Sarissa.__setReadyState__(oDoc, 4);
};
/**
* <p>Attached by an event handler to the load event. Internal use.</p>
* @private
*/
_sarissa_XMLDocument_onload = function(){
Sarissa.__handleLoad__(this);
};
/**
* <p>Sets the readyState property of the given DOM Document object.
* Internal use.</p>
* @private
* @argument oDoc the DOM Document object to fire the
* readystatechange event
* @argument iReadyState the number to change the readystate property to
*/
Sarissa.__setReadyState__ = function(oDoc, iReadyState){
oDoc.readyState = iReadyState;
if (oDoc.onreadystatechange != null && typeof oDoc.onreadystatechange == "function")
oDoc.onreadystatechange();
};
Sarissa.getDomDocument = function(sUri, sName){
var oDoc = document.implementation.createDocument(sUri?sUri:"", sName?sName:"", null);
oDoc.addEventListener("load", _sarissa_XMLDocument_onload, false);
return oDoc;
};
if(false && window.XMLDocument){
/**
* <p>Emulate IE's onreadystatechange attribute</p>
*/
XMLDocument.prototype.onreadystatechange = null;
/**
* <p>Emulates IE's readyState property, which always gives an integer from 0 to 4:</p>
* <ul><li>1 == LOADING,</li>
* <li>2 == LOADED,</li>
* <li>3 == INTERACTIVE,</li>
* <li>4 == COMPLETED</li></ul>
*/
XMLDocument.prototype.readyState = 0;
/**
* <p>Emulate IE's parseError attribute</p>
*/
XMLDocument.prototype.parseError = 0;
// NOTE: setting async to false will only work with documents
// called over HTTP (meaning a server), not the local file system,
// unless you are using Moz 1.4+.
// BTW the try>catch block is for 1.4; I haven't found a way to check if
// the property is implemented without
// causing an error and I dont want to use user agent stuff for that...
var _SARISSA_SYNC_NON_IMPLEMENTED = false;// ("async" in XMLDocument.prototype) ? false: true;
/**
* <p>Keeps a handle to the original load() method. Internal use and only
* if Mozilla version is lower than 1.4</p>
* @private
*/
XMLDocument.prototype._sarissa_load = XMLDocument.prototype.load;
/**
* <p>Overrides the original load method to provide synchronous loading for
* Mozilla versions prior to 1.4, using an XMLHttpRequest object (if
* async is set to false)</p>
* @returns the DOM Object as it was before the load() call (may be empty)
*/
XMLDocument.prototype.load = function(sURI) {
var oDoc = document.implementation.createDocument("", "", null);
Sarissa.copyChildNodes(this, oDoc);
this.parseError = 0;
Sarissa.__setReadyState__(this, 1);
try {
if(this.async == false && _SARISSA_SYNC_NON_IMPLEMENTED) {
var tmp = new XMLHttpRequest();
tmp.open("GET", sURI, false);
tmp.send(null);
Sarissa.__setReadyState__(this, 2);
Sarissa.copyChildNodes(tmp.responseXML, this);
Sarissa.__setReadyState__(this, 3);
}
else {
this._sarissa_load(sURI);
};
}
catch (objException) {
this.parseError = -1;
}
finally {
if(this.async == false){
Sarissa.__handleLoad__(this);
};
};
return oDoc;
};
}//if(window.XMLDocument)
else if(document.implementation && document.implementation.hasFeature && document.implementation.hasFeature('LS', '3.0')){
Document.prototype.async = true;
Document.prototype.onreadystatechange = null;
Document.prototype.parseError = 0;
Document.prototype.load = function(sURI) {
var parser = document.implementation.createLSParser(this.async ? document.implementation.MODE_ASYNCHRONOUS : document.implementation.MODE_SYNCHRONOUS, null);
if(this.async){
var self = this;
parser.addEventListener("load",
function(e) {
self.readyState = 4;
Sarissa.copyChildNodes(e.newDocument, self.documentElement, false);
self.onreadystatechange.call();
},
false);
};
try {
var oDoc = parser.parseURI(sURI);
}
catch(e){
this.parseError = -1;
};
if(!this.async)
Sarissa.copyChildNodes(oDoc, this.documentElement, false);
return oDoc;
};
/**
* <p>Factory method to obtain a new DOM Document object</p>
* @argument sUri the namespace of the root node (if any)
* @argument sUri the local name of the root node (if any)
* @returns a new DOM Document
*/
Sarissa.getDomDocument = function(sUri, sName){
return document.implementation.createDocument(sUri?sUri:"", sName?sName:"", null);
};
};
};//if(_SARISSA_HAS_DOM_CREATE_DOCUMENT)
};
//==========================================
// Common stuff
//==========================================
if(!window.DOMParser){
/*
* DOMParser is a utility class, used to construct DOMDocuments from XML strings
* @constructor
*/
DOMParser = function() {
};
if(_SARISSA_IS_SAFARI){
/**
* Construct a new DOM Document from the given XMLstring
* @param sXml the given XML string
* @param contentType the content type of the document the given string represents (one of text/xml, application/xml, application/xhtml+xml).
* @return a new DOM Document from the given XML string
*/
DOMParser.prototype.parseFromString = function(sXml, contentType){
if(contentType.toLowerCase() != "application/xml"){
throw "Cannot handle content type: \"" + contentType + "\"";
};
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "data:text/xml;charset=utf-8," + encodeURIComponent(str), false);
xmlhttp.send(null);
return xmlhttp.responseXML;
};
}else if(Sarissa.getDomDocument && Sarissa.getDomDocument() && "loadXML" in Sarissa.getDomDocument()){
DOMParser.prototype.parseFromString = function(sXml, contentType){
var doc = Sarissa.getDomDocument();
doc.loadXML(sXml);
return doc;
};
};
};
if(window.XMLHttpRequest){
Sarissa.IS_ENABLED_XMLHTTP = true;
}
else if(_SARISSA_IS_IE){
/**
* Emulate XMLHttpRequest
* @constructor
*/
XMLHttpRequest = function() {
return new ActiveXObject(_SARISSA_XMLHTTP_PROGID);
};
Sarissa.IS_ENABLED_XMLHTTP = true;
};
if(!window.document.importNode && _SARISSA_IS_IE){
try{
/**
* Implements importNode for the current window document in IE using innerHTML.
* Testing showed that DOM was multiple times slower than innerHTML for this,
* sorry folks. If you encounter trouble (who knows what IE does behind innerHTML)
* please gimme a call.
* @param oNode the Node to import
* @param bChildren whether to include the children of oNode
* @returns the imported node for further use
*/
window.document.importNode = function(oNode, bChildren){
var importNode = document.createElement("div");
if(bChildren)
importNode.innerHTML = Sarissa.serialize(oNode);
else
importNode.innerHTML = Sarissa.serialize(oNode.cloneNode(false));
return importNode.firstChild;
};
}catch(e){};
};
if(!Sarissa.getParseErrorText){
/**
* <p>Returns a human readable description of the parsing error. Usefull
* for debugging. Tip: append the returned error string in a &lt;pre&gt;
* element if you want to render it.</p>
* <p>Many thanks to Christian Stocker for the initial patch.</p>
* @argument oDoc The target DOM document
* @returns The parsing error description of the target Document in
* human readable form (preformated text)
*/
Sarissa.getParseErrorText = function (oDoc){
var parseErrorText = Sarissa.PARSED_OK;
if(oDoc && oDoc.parseError && oDoc.parseError != 0){
/*moz*/
if(oDoc.documentElement.tagName == "parsererror"){
parseErrorText = oDoc.documentElement.firstChild.data;
parseErrorText += "\n" + oDoc.documentElement.firstChild.nextSibling.firstChild.data;
}/*konq*/
else{
parseErrorText = Sarissa.getText(oDoc.documentElement);/*.getElementsByTagName("h1")[0], false) + "\n";
parseErrorText += Sarissa.getText(oDoc.documentElement.getElementsByTagName("body")[0], false) + "\n";
parseErrorText += Sarissa.getText(oDoc.documentElement.getElementsByTagName("pre")[0], false);*/
};
};
return parseErrorText;
};
};
Sarissa.getText = function(oNode, deep){
var s = "";
var nodes = oNode.childNodes;
for(var i=0; i < nodes.length; i++){
var node = nodes[i];
var nodeType = node.nodeType;
if(nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE){
s += node.data;
}else if(deep == true
&& (nodeType == Node.ELEMENT_NODE
|| nodeType == Node.DOCUMENT_NODE
|| nodeType == Node.DOCUMENT_FRAGMENT_NODE)){
s += Sarissa.getText(node, true);
};
};
return s;
};
if(window.XMLSerializer){
/**
* <p>Factory method to obtain the serialization of a DOM Node</p>
* @returns the serialized Node as an XML string
*/
Sarissa.serialize = function(oDoc){
var s = null;
if(oDoc){
s = oDoc.innerHTML?oDoc.innerHTML:(new XMLSerializer()).serializeToString(oDoc);
};
return s;
};
}else{
if(Sarissa.getDomDocument && (Sarissa.getDomDocument("","foo", null)).xml){
// see non-IE version
Sarissa.serialize = function(oDoc) {
var s = null;
if(oDoc){
s = oDoc.innerHTML?oDoc.innerHTML:oDoc.xml;
};
return s;
};
/**
* Utility class to serialize DOM Node objects to XML strings
* @constructor
*/
XMLSerializer = function(){};
/**
* Serialize the given DOM Node to an XML string
* @param oNode the DOM Node to serialize
*/
XMLSerializer.prototype.serializeToString = function(oNode) {
return oNode.xml;
};
};
};
/**
* strips tags from a markup string
*/
Sarissa.stripTags = function (s) {
return s.replace(/<[^>]+>/g,"");
};
/**
* <p>Deletes all child nodes of the given node</p>
* @argument oNode the Node to empty
*/
Sarissa.clearChildNodes = function(oNode) {
// need to check for firstChild due to opera 8 bug with hasChildNodes
while(oNode.firstChild){
oNode.removeChild(oNode.firstChild);
};
};
/**
* <p> Copies the childNodes of nodeFrom to nodeTo</p>
* <p> <b>Note:</b> The second object's original content is deleted before
* the copy operation, unless you supply a true third parameter</p>
* @argument nodeFrom the Node to copy the childNodes from
* @argument nodeTo the Node to copy the childNodes to
* @argument bPreserveExisting whether to preserve the original content of nodeTo, default is false
*/
Sarissa.copyChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
if((!nodeFrom) || (!nodeTo)){
throw "Both source and destination nodes must be provided";
};
if(!bPreserveExisting){
Sarissa.clearChildNodes(nodeTo);
};
var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
var nodes = nodeFrom.childNodes;
if(ownerDoc.importNode && (!_SARISSA_IS_IE)) {
for(var i=0;i < nodes.length;i++) {
nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
};
}
else{
for(var i=0;i < nodes.length;i++) {
nodeTo.appendChild(nodes[i].cloneNode(true));
};
};
};
/**
* <p> Moves the childNodes of nodeFrom to nodeTo</p>
* <p> <b>Note:</b> The second object's original content is deleted before
* the move operation, unless you supply a true third parameter</p>
* @argument nodeFrom the Node to copy the childNodes from
* @argument nodeTo the Node to copy the childNodes to
* @argument bPreserveExisting whether to preserve the original content of nodeTo, default is
*/
Sarissa.moveChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
if((!nodeFrom) || (!nodeTo)){
throw "Both source and destination nodes must be provided";
};
if(!bPreserveExisting){
Sarissa.clearChildNodes(nodeTo);
};
var nodes = nodeFrom.childNodes;
// if within the same doc, just move, else copy and delete
if(nodeFrom.ownerDocument == nodeTo.ownerDocument){
while(nodeFrom.firstChild){
nodeTo.appendChild(nodeFrom.firstChild);
};
}else{
var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
if(ownerDoc.importNode && (!_SARISSA_IS_IE)) {
for(var i=0;i < nodes.length;i++) {
nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
};
}else{
for(var i=0;i < nodes.length;i++) {
nodeTo.appendChild(nodes[i].cloneNode(true));
};
};
Sarissa.clearChildNodes(nodeFrom);
};
};
/**
* <p>Serialize any object to an XML string. All properties are serialized using the property name
* as the XML element name. Array elements are rendered as <code>array-item</code> elements,
* using their index/key as the value of the <code>key</code> attribute.</p>
* @argument anyObject the object to serialize
* @argument objectName a name for that object
* @return the XML serializationj of the given object as a string
*/
Sarissa.xmlize = function(anyObject, objectName, indentSpace){
indentSpace = indentSpace?indentSpace:'';
var s = indentSpace + '<' + objectName + '>';
var isLeaf = false;
if(!(anyObject instanceof Object) || anyObject instanceof Number || anyObject instanceof String
|| anyObject instanceof Boolean || anyObject instanceof Date){
s += Sarissa.escape(""+anyObject);
isLeaf = true;
}else{
s += "\n";
var itemKey = '';
var isArrayItem = anyObject instanceof Array;
for(var name in anyObject){
s += Sarissa.xmlize(anyObject[name], (isArrayItem?"array-item key=\""+name+"\"":name), indentSpace + " ");
};
s += indentSpace;
};
return s += (objectName.indexOf(' ')!=-1?"</array-item>\n":"</" + objectName + ">\n");
};
/**
* Escape the given string chacters that correspond to the five predefined XML entities
* @param sXml the string to escape
*/
Sarissa.escape = function(sXml){
return sXml.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
};
/**
* Unescape the given string. This turns the occurences of the predefined XML
* entities to become the characters they represent correspond to the five predefined XML entities
* @param sXml the string to unescape
*/
Sarissa.unescape = function(sXml){
return sXml.replace(/&apos;/g,"'")
.replace(/&quot;/g,"\"")
.replace(/&gt;/g,">")
.replace(/&lt;/g,"<")
.replace(/&amp;/g,"&");
};
// EOF

View File

@ -0,0 +1,105 @@
/**
* ====================================================================
* About
* ====================================================================
* Sarissa cross browser XML library - AJAX module
* @version 0.9.6.1
* @author: Copyright Manos Batsis, mailto: mbatsis at users full stop sourceforge full stop net
*
* This module contains some convinient AJAX tricks based on Sarissa
*
* ====================================================================
* Licence
* ====================================================================
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* the GNU Lesser General Public License version 2.1 as published by
* the Free Software Foundation (your choice between the two).
*
* 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 or GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* or GNU Lesser General Public License along with this program; if not,
* write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* or visit http://www.gnu.org
*
*/
/**
* Update an element with response of a GET request on the given URL.
* @addon
* @param sFromUrl the URL to make the request to
* @param oTargetElement the element to update
* @param xsltproc (optional) the transformer to use on the returned
* content before updating the target element with it
*/
Sarissa.updateContentFromURI = function(sFromUrl, oTargetElement, xsltproc) {
try{
oTargetElement.style.cursor = "wait";
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", sFromUrl);
function sarissa_dhtml_loadHandler() {
if (xmlhttp.readyState == 4) {
oTargetElement.style.cursor = "auto";
Sarissa.updateContentFromNode(xmlhttp.responseXML, oTargetElement, xsltproc);
};
};
xmlhttp.onreadystatechange = sarissa_dhtml_loadHandler;
xmlhttp.send(null);
oTargetElement.style.cursor = "auto";
}
catch(e){
oTargetElement.style.cursor = "auto";
throw e;
};
};
/**
* Update an element's content with the given DOM node.
* @addon
* @param sFromUrl the URL to make the request to
* @param oTargetElement the element to update
* @param xsltproc (optional) the transformer to use on the given
* DOM node before updating the target element with it
*/
Sarissa.updateContentFromNode = function(oNode, oTargetElement, xsltproc) {
try {
oTargetElement.style.cursor = "wait";
Sarissa.clearChildNodes(oTargetElement);
// check for parsing errors
var ownerDoc = oNode.nodeType == Node.DOCUMENT_NODE?oNode:oNode.ownerDocument;
if(ownerDoc.parseError && ownerDoc.parseError != 0) {
var pre = document.createElement("pre");
pre.appendChild(document.createTextNode(Sarissa.getParseErrorText(ownerDoc)));
oTargetElement.appendChild(pre);
}
else {
// transform if appropriate
if(xsltproc) {
oNode = xsltproc.transformToDocument(oNode);
};
// be smart, maybe the user wants to display the source instead
if(oTargetElement.tagName.toLowerCase == "textarea" || oTargetElement.tagName.toLowerCase == "input") {
oTargetElement.value = Sarissa.serialize(oNode);
}
else {
// ok that was not smart; it was paranoid. Keep up the good work by trying to use DOM instead of innerHTML
if(oNode.nodeType == Node.DOCUMENT_NODE || oNode.ownerDocument.documentElement == oNode) {
oTargetElement.innerHTML = Sarissa.serialize(oNode);
}
else{
oTargetElement.appendChild(oTargetElement.ownerDocument.importNode(oNode, true));
};
};
};
}
catch(e) {
throw e;
}
finally{
oTargetElement.style.cursor = "auto";
};
};

164
tools/euca-get-ajax-console Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env python
# pylint: disable-msg=C0103
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Euca add-on to use ajax console"""
import getopt
import os
import sys
# If ../nova/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
import boto
import nova
from boto.ec2.connection import EC2Connection
from euca2ools import Euca2ool, InstanceValidationError, Util, ConnectionFailed
usage_string = """
Retrieves a url to an ajax console terminal
euca-get-ajax-console [-h, --help] [--version] [--debug] instance_id
REQUIRED PARAMETERS
instance_id: unique identifier for the instance show the console output for.
OPTIONAL PARAMETERS
"""
# This class extends boto to add AjaxConsole functionality
class NovaEC2Connection(EC2Connection):
def get_ajax_console(self, instance_id):
"""
Retrieves a console connection for the specified instance.
:type instance_id: string
:param instance_id: The instance ID of a running instance on the cloud.
:rtype: :class:`AjaxConsole`
"""
class AjaxConsole:
def __init__(self, parent=None):
self.parent = parent
self.instance_id = None
self.url = None
def startElement(self, name, attrs, connection):
return None
def endElement(self, name, value, connection):
if name == 'instanceId':
self.instance_id = value
elif name == 'url':
self.url = value
else:
setattr(self, name, value)
params = {}
self.build_list_params(params, [instance_id], 'InstanceId')
return self.get_object('GetAjaxConsole', params, AjaxConsole)
pass
def override_connect_ec2(aws_access_key_id=None,
aws_secret_access_key=None, **kwargs):
return NovaEC2Connection(aws_access_key_id,
aws_secret_access_key, **kwargs)
# override boto's connect_ec2 method, so that we can use NovaEC2Connection
boto.connect_ec2 = override_connect_ec2
def usage(status=1):
print usage_string
Util().usage()
sys.exit(status)
def version():
print Util().version()
sys.exit()
def display_console_output(console_output):
print console_output.instance_id
print console_output.timestamp
print console_output.output
def display_ajax_console_output(console_output):
print console_output.url
def main():
try:
euca = Euca2ool()
except Exception, e:
print e
usage()
instance_id = None
for name, value in euca.opts:
if name in ('-h', '--help'):
usage(0)
elif name == '--version':
version()
elif name == '--debug':
debug = True
for arg in euca.args:
instance_id = arg
break
if instance_id:
try:
euca.validate_instance_id(instance_id)
except InstanceValidationError:
print 'Invalid instance id'
sys.exit(1)
try:
euca_conn = euca.make_connection()
except ConnectionFailed, e:
print e.message
sys.exit(1)
try:
console_output = euca_conn.get_ajax_console(instance_id)
except Exception, ex:
euca.display_error_and_exit('%s' % ex)
display_ajax_console_output(console_output)
else:
print 'instance_id must be specified'
usage()
if __name__ == "__main__":
main()