• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2##!/usr/bin/env python
3"""CGI shell server
4
5This exposes a shell terminal on a web page.
6It uses AJAX to send keys and receive screen updates.
7The client web browser needs nothing but CSS and Javascript.
8
9    --hostname : sets the remote host name to open an ssh connection to.
10    --username : sets the user name to login with
11    --password : (optional) sets the password to login with
12    --port     : set the local port for the server to listen on
13    --watch    : show the virtual screen after each client request
14
15This project is probably not the most security concious thing I've ever built.
16This should be considered an experimental tool -- at best.
17"""
18import sys,os
19sys.path.insert (0,os.getcwd()) # let local modules precede any installed modules
20import socket, random, string, traceback, cgi, time, getopt, getpass, threading, resource, signal
21import pxssh, pexpect, ANSI
22
23def exit_with_usage(exit_code=1):
24    print globals()['__doc__']
25    os._exit(exit_code)
26
27def client (command, host='localhost', port=-1):
28    """This sends a request to the server and returns the response.
29    If port <= 0 then host is assumed to be the filename of a Unix domain socket.
30    If port > 0 then host is an inet hostname.
31    """
32    if port <= 0:
33        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
34        s.connect(host)
35    else:
36        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
37        s.connect((host, port))
38    s.send(command)
39    data = s.recv (2500)
40    s.close()
41    return data
42
43def server (hostname, username, password, socket_filename='/tmp/server_sock', daemon_mode = True, verbose=False):
44    """This starts and services requests from a client.
45        If daemon_mode is True then this forks off a separate daemon process and returns the daemon's pid.
46        If daemon_mode is False then this does not return until the server is done.
47    """
48    if daemon_mode:
49        mypid_name = '/tmp/%d.pid' % os.getpid()
50        daemon_pid = daemonize(daemon_pid_filename=mypid_name)
51        time.sleep(1)
52        if daemon_pid != 0:
53            os.unlink(mypid_name)
54            return daemon_pid
55
56    virtual_screen = ANSI.ANSI (24,80)
57    child = pxssh.pxssh()
58    try:
59        child.login (hostname, username, password, login_naked=True)
60    except:
61        return
62    if verbose: print 'login OK'
63    virtual_screen.write (child.before)
64    virtual_screen.write (child.after)
65
66    if os.path.exists(socket_filename): os.remove(socket_filename)
67    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
68    s.bind(socket_filename)
69    os.chmod(socket_filename, 0777)
70    if verbose: print 'Listen'
71    s.listen(1)
72
73    r = roller (endless_poll, (child, child.PROMPT, virtual_screen))
74    r.start()
75    if verbose: print "started screen-poll-updater in background thread"
76    sys.stdout.flush()
77    try:
78        while True:
79            conn, addr = s.accept()
80            if verbose: print 'Connected by', addr
81            data = conn.recv(1024)
82            request = data.split(' ', 1)
83            if len(request)>1:
84                cmd = request[0].strip()
85                arg = request[1].strip()
86            else:
87                cmd = request[0].strip()
88                arg = ''
89
90            if cmd == 'exit':
91                r.cancel()
92                break
93            elif cmd == 'sendline':
94                child.sendline (arg)
95                time.sleep(0.1)
96                shell_window = str(virtual_screen)
97            elif cmd == 'send' or cmd=='xsend':
98                if cmd=='xsend':
99                    arg = arg.decode("hex")
100                child.send (arg)
101                time.sleep(0.1)
102                shell_window = str(virtual_screen)
103            elif cmd == 'cursor':
104                shell_window = '%x,%x' % (virtual_screen.cur_r, virtual_screen.cur_c)
105            elif cmd == 'refresh':
106                shell_window = str(virtual_screen)
107            elif cmd == 'hash':
108                shell_window = str(hash(str(virtual_screen)))
109
110            response = []
111            response.append (shell_window)
112            if verbose: print '\n'.join(response)
113            sent = conn.send('\n'.join(response))
114            if sent < len (response):
115                if verbose: print "Sent is too short. Some data was cut off."
116            conn.close()
117    except e:
118        pass
119    r.cancel()
120    if verbose: print "cleaning up socket"
121    s.close()
122    if os.path.exists(socket_filename): os.remove(socket_filename)
123    if verbose: print "server done!"
124
125class roller (threading.Thread):
126    """This class continuously loops a function in a thread.
127        This is basically a thin layer around Thread with a
128        while loop and a cancel.
129    """
130    def __init__(self, function, args=[], kwargs={}):
131        threading.Thread.__init__(self)
132        self.function = function
133        self.args = args
134        self.kwargs = kwargs
135        self.finished = threading.Event()
136    def cancel(self):
137        """Stop the roller."""
138        self.finished.set()
139    def run(self):
140        while not self.finished.isSet():
141            self.function(*self.args, **self.kwargs)
142
143def endless_poll (child, prompt, screen, refresh_timeout=0.1):
144    """This keeps the screen updated with the output of the child.
145        This will be run in a separate thread. See roller class.
146    """
147    #child.logfile_read = screen
148    try:
149        s = child.read_nonblocking(4000, 0.1)
150        screen.write(s)
151    except:
152        pass
153
154def daemonize (stdin=None, stdout=None, stderr=None, daemon_pid_filename=None):
155    """This runs the current process in the background as a daemon.
156    The arguments stdin, stdout, stderr allow you to set the filename that the daemon reads and writes to.
157    If they are set to None then all stdio for the daemon will be directed to /dev/null.
158    If daemon_pid_filename is set then the pid of the daemon will be written to it as plain text
159    and the pid will be returned. If daemon_pid_filename is None then this will return None.
160    """
161    UMASK = 0
162    WORKINGDIR = "/"
163    MAXFD = 1024
164
165    # The stdio file descriptors are redirected to /dev/null by default.
166    if hasattr(os, "devnull"):
167        DEVNULL = os.devnull
168    else:
169        DEVNULL = "/dev/null"
170    if stdin is None: stdin = DEVNULL
171    if stdout is None: stdout = DEVNULL
172    if stderr is None: stderr = DEVNULL
173
174    try:
175        pid = os.fork()
176    except OSError, e:
177        raise Exception, "%s [%d]" % (e.strerror, e.errno)
178
179    if pid != 0:   # The first child.
180        os.waitpid(pid,0)
181        if daemon_pid_filename is not None:
182            daemon_pid = int(file(daemon_pid_filename,'r').read())
183            return daemon_pid
184        else:
185            return None
186
187    # first child
188    os.setsid()
189    signal.signal(signal.SIGHUP, signal.SIG_IGN)
190
191    try:
192        pid = os.fork() # fork second child
193    except OSError, e:
194        raise Exception, "%s [%d]" % (e.strerror, e.errno)
195
196    if pid != 0:
197        if daemon_pid_filename is not None:
198            file(daemon_pid_filename,'w').write(str(pid))
199        os._exit(0) # exit parent (the first child) of the second child.
200
201    # second child
202    os.chdir(WORKINGDIR)
203    os.umask(UMASK)
204
205    maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
206    if maxfd == resource.RLIM_INFINITY:
207        maxfd = MAXFD
208
209    # close all file descriptors
210    for fd in xrange(0, maxfd):
211        try:
212            os.close(fd)
213        except OSError:   # fd wasn't open to begin with (ignored)
214            pass
215
216    os.open (DEVNULL, os.O_RDWR)  # standard input
217
218    # redirect standard file descriptors
219    si = open(stdin, 'r')
220    so = open(stdout, 'a+')
221    se = open(stderr, 'a+', 0)
222    os.dup2(si.fileno(), sys.stdin.fileno())
223    os.dup2(so.fileno(), sys.stdout.fileno())
224    os.dup2(se.fileno(), sys.stderr.fileno())
225
226    return 0
227
228def client_cgi ():
229    """This handles the request if this script was called as a cgi.
230    """
231    sys.stderr = sys.stdout
232    ajax_mode = False
233    TITLE="Shell"
234    SHELL_OUTPUT=""
235    SID="NOT"
236    print "Content-type: text/html;charset=utf-8\r\n"
237    try:
238        form = cgi.FieldStorage()
239        if form.has_key('ajax'):
240            ajax_mode = True
241            ajax_cmd = form['ajax'].value
242            SID=form['sid'].value
243            if ajax_cmd == 'send':
244                command = 'xsend'
245                arg = form['arg'].value.encode('hex')
246                result = client (command + ' ' + arg, '/tmp/'+SID)
247                print result
248            elif ajax_cmd == 'refresh':
249                command = 'refresh'
250                result = client (command, '/tmp/'+SID)
251                print result
252            elif ajax_cmd == 'cursor':
253                command = 'cursor'
254                result = client (command, '/tmp/'+SID)
255                print result
256            elif ajax_cmd == 'exit':
257                command = 'exit'
258                result = client (command, '/tmp/'+SID)
259                print result
260            elif ajax_cmd == 'hash':
261                command = 'hash'
262                result = client (command, '/tmp/'+SID)
263                print result
264        elif not form.has_key('sid'):
265            SID=random_sid()
266            print LOGIN_HTML % locals();
267        else:
268            SID=form['sid'].value
269            if form.has_key('start_server'):
270                USERNAME = form['username'].value
271                PASSWORD = form['password'].value
272                dpid = server ('127.0.0.1', USERNAME, PASSWORD, '/tmp/'+SID)
273                SHELL_OUTPUT="daemon pid: " + str(dpid)
274            else:
275                if form.has_key('cli'):
276                    command = 'sendline ' + form['cli'].value
277                else:
278                    command = 'sendline'
279                SHELL_OUTPUT = client (command, '/tmp/'+SID)
280            print CGISH_HTML % locals()
281    except:
282        tb_dump = traceback.format_exc()
283        if ajax_mode:
284            print str(tb_dump)
285        else:
286            SHELL_OUTPUT=str(tb_dump)
287            print CGISH_HTML % locals()
288
289def server_cli():
290    """This is the command line interface to starting the server.
291    This handles things if the script was not called as a CGI
292    (if you run it from the command line).
293    """
294    try:
295        optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch'])
296    except Exception, e:
297        print str(e)
298        exit_with_usage()
299
300    command_line_options = dict(optlist)
301    options = dict(optlist)
302    # There are a million ways to cry for help. These are but a few of them.
303    if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]:
304        exit_with_usage(0)
305
306    hostname = "127.0.0.1"
307    #port = 1664
308    username = os.getenv('USER')
309    password = ""
310    daemon_mode = False
311    if '-d' in options:
312        daemon_mode = True
313    if '--watch' in options:
314        watch_mode = True
315    else:
316        watch_mode = False
317    if '--hostname' in options:
318        hostname = options['--hostname']
319    if '--port' in options:
320        port = int(options['--port'])
321    if '--username' in options:
322        username = options['--username']
323    if '--password' in options:
324        password = options['--password']
325    else:
326        password = getpass.getpass('password: ')
327
328    server (hostname, username, password, '/tmp/mysock', daemon_mode)
329
330def random_sid ():
331    a=random.randint(0,65535)
332    b=random.randint(0,65535)
333    return '%04x%04x.sid' % (a,b)
334
335def parse_host_connect_string (hcs):
336    """This parses a host connection string in the form
337    username:password@hostname:port. All fields are options expcet hostname. A
338    dictionary is returned with all four keys. Keys that were not included are
339    set to empty strings ''. Note that if your password has the '@' character
340    then you must backslash escape it.
341    """
342    if '@' in hcs:
343        p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
344    else:
345        p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
346    m = p.search (hcs)
347    d = m.groupdict()
348    d['password'] = d['password'].replace('\\@','@')
349    return d
350
351def pretty_box (s, rows=24, cols=80):
352    """This puts an ASCII text box around the given string.
353    """
354    top_bot = '+' + '-'*cols + '+\n'
355    return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot
356
357def main ():
358    if os.getenv('REQUEST_METHOD') is None:
359        server_cli()
360    else:
361        client_cgi()
362
363# It's mostly HTML and Javascript from here on out.
364CGISH_HTML="""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
365<html>
366<head>
367<title>%(TITLE)s %(SID)s</title>
368<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
369<style type=text/css>
370a {color: #9f9; text-decoration: none}
371a:hover {color: #0f0}
372hr {color: #0f0}
373html,body,textarea,input,form
374{
375font-family: "Courier New", Courier, mono;
376font-size: 8pt;
377color: #0c0;
378background-color: #020;
379margin:0;
380padding:0;
381border:0;
382}
383input { background-color: #010; }
384textarea {
385border-width:1;
386border-style:solid;
387border-color:#0c0;
388padding:3;
389margin:3;
390}
391</style>
392
393<script language="JavaScript">
394function focus_first()
395{if (document.forms.length > 0)
396{var TForm = document.forms[0];
397for (i=0;i<TForm.length;i++){
398if ((TForm.elements[i].type=="text")||
399(TForm.elements[i].type=="textarea")||
400(TForm.elements[i].type.toString().charAt(0)=="s"))
401{document.forms[0].elements[i].focus();break;}}}}
402
403// JavaScript Virtual Keyboard
404// If you like this code then buy me a sandwich.
405// Noah Spurrier <noah@noah.org>
406var flag_shift=0;
407var flag_shiftlock=0;
408var flag_ctrl=0;
409var ButtonOnColor="#ee0";
410
411function init ()
412{
413    // hack to set quote key to show both single quote and double quote
414    document.form['quote'].value = "'" + '  "';
415    //refresh_screen();
416    poll();
417    document.form["cli"].focus();
418}
419function get_password ()
420{
421    var username = prompt("username?","");
422    var password = prompt("password?","");
423    start_server (username, password);
424}
425function multibrowser_ajax ()
426{
427    var xmlHttp = false;
428/*@cc_on @*/
429/*@if (@_jscript_version >= 5)
430    try
431    {
432        xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
433    }
434    catch (e)
435    {
436        try
437        {
438            xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
439        }
440        catch (e2)
441        {
442              xmlHttp = false;
443        }
444    }
445@end @*/
446
447    if (!xmlHttp && typeof XMLHttpRequest != 'undefined')
448    {
449        xmlHttp = new XMLHttpRequest();
450    }
451    return xmlHttp;
452}
453function load_url_to_screen(url)
454{
455    xmlhttp = multibrowser_ajax();
456    //window.XMLHttpRequest?new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP");
457    xmlhttp.onreadystatechange = update_virtual_screen;
458    xmlhttp.open("GET", url);
459    xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
460    xmlhttp.send(null);
461}
462function update_virtual_screen()
463{
464    if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200))
465    {
466        var screen_text = xmlhttp.responseText;
467        document.form["screen_text"].value = screen_text;
468        //var json_data = json_parse(xmlhttp.responseText);
469    }
470}
471function poll()
472{
473    refresh_screen();
474    timerID  = setTimeout("poll()", 2000);
475    // clearTimeout(timerID);
476}
477//function start_server (username, password)
478//{
479//    load_url_to_screen('cgishell.cgi?ajax=serverstart&username=' + escape(username) + '&password=' + escape(password);
480//}
481function refresh_screen()
482{
483    load_url_to_screen('cgishell.cgi?ajax=refresh&sid=%(SID)s');
484}
485function query_hash()
486{
487    load_url_to_screen('cgishell.cgi?ajax=hash&sid=%(SID)s');
488}
489function query_cursor()
490{
491    load_url_to_screen('cgishell.cgi?ajax=cursor&sid=%(SID)s');
492}
493function exit_server()
494{
495    load_url_to_screen('cgishell.cgi?ajax=exit&sid=%(SID)s');
496}
497function type_key (chars)
498{
499    var ch = '?';
500    if (flag_shiftlock || flag_shift)
501    {
502        ch = chars.substr(1,1);
503    }
504    else if (flag_ctrl)
505    {
506        ch = chars.substr(2,1);
507    }
508    else
509    {
510        ch = chars.substr(0,1);
511    }
512    load_url_to_screen('cgishell.cgi?ajax=send&sid=%(SID)s&arg=' + escape(ch));
513    if (flag_shift || flag_ctrl)
514    {
515        flag_shift = 0;
516        flag_ctrl = 0;
517    }
518    update_button_colors();
519}
520
521function key_shiftlock()
522{
523    flag_ctrl = 0;
524    flag_shift = 0;
525    if (flag_shiftlock)
526    {
527        flag_shiftlock = 0;
528    }
529    else
530    {
531        flag_shiftlock = 1;
532    }
533    update_button_colors();
534}
535
536function key_shift()
537{
538    if (flag_shift)
539    {
540        flag_shift = 0;
541    }
542    else
543    {
544        flag_ctrl = 0;
545        flag_shiftlock = 0;
546        flag_shift = 1;
547    }
548    update_button_colors();
549}
550function key_ctrl ()
551{
552    if (flag_ctrl)
553    {
554        flag_ctrl = 0;
555    }
556    else
557    {
558        flag_ctrl = 1;
559        flag_shiftlock = 0;
560        flag_shift = 0;
561    }
562
563    update_button_colors();
564}
565function update_button_colors ()
566{
567    if (flag_ctrl)
568    {
569        document.form['Ctrl'].style.backgroundColor = ButtonOnColor;
570        document.form['Ctrl2'].style.backgroundColor = ButtonOnColor;
571    }
572    else
573    {
574        document.form['Ctrl'].style.backgroundColor = document.form.style.backgroundColor;
575        document.form['Ctrl2'].style.backgroundColor = document.form.style.backgroundColor;
576    }
577    if (flag_shift)
578    {
579        document.form['Shift'].style.backgroundColor = ButtonOnColor;
580        document.form['Shift2'].style.backgroundColor = ButtonOnColor;
581    }
582    else
583    {
584        document.form['Shift'].style.backgroundColor = document.form.style.backgroundColor;
585        document.form['Shift2'].style.backgroundColor = document.form.style.backgroundColor;
586    }
587    if (flag_shiftlock)
588    {
589        document.form['ShiftLock'].style.backgroundColor = ButtonOnColor;
590    }
591    else
592    {
593        document.form['ShiftLock'].style.backgroundColor = document.form.style.backgroundColor;
594    }
595
596}
597function keyHandler(e)
598{
599    var pressedKey;
600    if (document.all)    { e = window.event; }
601    if (document.layers) { pressedKey = e.which; }
602    if (document.all)    { pressedKey = e.keyCode; }
603    pressedCharacter = String.fromCharCode(pressedKey);
604    type_key(pressedCharacter+pressedCharacter+pressedCharacter);
605    alert(pressedCharacter);
606//    alert(' Character = ' + pressedCharacter + ' [Decimal value = ' + pressedKey + ']');
607}
608//document.onkeypress = keyHandler;
609//if (document.layers)
610//    document.captureEvents(Event.KEYPRESS);
611//http://sniptools.com/jskeys
612//document.onkeyup = KeyCheck;
613function KeyCheck(e)
614{
615    var KeyID = (window.event) ? event.keyCode : e.keyCode;
616    type_key(String.fromCharCode(KeyID));
617    e.cancelBubble = true;
618    window.event.cancelBubble = true;
619}
620</script>
621
622</head>
623
624<body onload="init()">
625<form id="form" name="form" action="/cgi-bin/cgishell.cgi" method="POST">
626<input name="sid" value="%(SID)s" type="hidden">
627<textarea name="screen_text" cols="81" rows="25">%(SHELL_OUTPUT)s</textarea>
628<hr noshade="1">
629&nbsp;<input name="cli" id="cli" type="text" size="80"><br>
630<table border="0" align="left">
631<tr>
632<td width="86%%" align="center">
633    <input name="submit" type="submit" value="Submit">
634    <input name="refresh" type="button" value="REFRESH" onclick="refresh_screen()">
635    <input name="refresh" type="button" value="CURSOR" onclick="query_cursor()">
636    <input name="hash" type="button" value="HASH" onclick="query_hash()">
637    <input name="exit" type="button" value="EXIT" onclick="exit_server()">
638    <br>
639    <input type="button" value="Esc" onclick="type_key('\\x1b\\x1b')" />
640    <input type="button" value="` ~" onclick="type_key('`~')" />
641    <input type="button" value="1!" onclick="type_key('1!')" />
642    <input type="button" value="2@" onclick="type_key('2@\\x00')" />
643    <input type="button" value="3#" onclick="type_key('3#')" />
644    <input type="button" value="4$" onclick="type_key('4$')" />
645    <input type="button" value="5%%" onclick="type_key('5%%')" />
646    <input type="button" value="6^" onclick="type_key('6^\\x1E')" />
647    <input type="button" value="7&" onclick="type_key('7&')" />
648    <input type="button" value="8*" onclick="type_key('8*')" />
649    <input type="button" value="9(" onclick="type_key('9(')" />
650    <input type="button" value="0)" onclick="type_key('0)')" />
651    <input type="button" value="-_" onclick="type_key('-_\\x1F')" />
652    <input type="button" value="=+" onclick="type_key('=+')" />
653    <input type="button" value="BkSp" onclick="type_key('\\x08\\x08\\x08')" />
654    <br>
655    <input type="button" value="Tab" onclick="type_key('\\t\\t')" />
656    <input type="button" value="Q" onclick="type_key('qQ\\x11')" />
657    <input type="button" value="W" onclick="type_key('wW\\x17')" />
658    <input type="button" value="E" onclick="type_key('eE\\x05')" />
659    <input type="button" value="R" onclick="type_key('rR\\x12')" />
660    <input type="button" value="T" onclick="type_key('tT\\x14')" />
661    <input type="button" value="Y" onclick="type_key('yY\\x19')" />
662    <input type="button" value="U" onclick="type_key('uU\\x15')" />
663    <input type="button" value="I" onclick="type_key('iI\\x09')" />
664    <input type="button" value="O" onclick="type_key('oO\\x0F')" />
665    <input type="button" value="P" onclick="type_key('pP\\x10')" />
666    <input type="button" value="[ {" onclick="type_key('[{\\x1b')" />
667    <input type="button" value="] }" onclick="type_key(']}\\x1d')" />
668    <input type="button" value="\\ |" onclick="type_key('\\\\|\\x1c')" />
669    <br>
670    <input type="button" id="Ctrl" value="Ctrl" onclick="key_ctrl()" />
671    <input type="button" value="A" onclick="type_key('aA\\x01')" />
672    <input type="button" value="S" onclick="type_key('sS\\x13')" />
673    <input type="button" value="D" onclick="type_key('dD\\x04')" />
674    <input type="button" value="F" onclick="type_key('fF\\x06')" />
675    <input type="button" value="G" onclick="type_key('gG\\x07')" />
676    <input type="button" value="H" onclick="type_key('hH\\x08')" />
677    <input type="button" value="J" onclick="type_key('jJ\\x0A')" />
678    <input type="button" value="K" onclick="type_key('kK\\x0B')" />
679    <input type="button" value="L" onclick="type_key('lL\\x0C')" />
680    <input type="button" value="; :" onclick="type_key(';:')" />
681    <input type="button" id="quote" value="'" onclick="type_key('\\x27\\x22')" />
682    <input type="button" value="Enter" onclick="type_key('\\n\\n')" />
683    <br>
684    <input type="button" id="ShiftLock" value="Caps Lock" onclick="key_shiftlock()" />
685    <input type="button" id="Shift" value="Shift" onclick="key_shift()"  />
686    <input type="button" value="Z" onclick="type_key('zZ\\x1A')" />
687    <input type="button" value="X" onclick="type_key('xX\\x18')" />
688    <input type="button" value="C" onclick="type_key('cC\\x03')" />
689    <input type="button" value="V" onclick="type_key('vV\\x16')" />
690    <input type="button" value="B" onclick="type_key('bB\\x02')" />
691    <input type="button" value="N" onclick="type_key('nN\\x0E')" />
692    <input type="button" value="M" onclick="type_key('mM\\x0D')" />
693    <input type="button" value=", <" onclick="type_key(',<')" />
694    <input type="button" value=". >" onclick="type_key('.>')" />
695    <input type="button" value="/ ?" onclick="type_key('/?')" />
696    <input type="button" id="Shift2" value="Shift" onclick="key_shift()" />
697    <input type="button" id="Ctrl2" value="Ctrl" onclick="key_ctrl()" />
698    <br>
699    <input type="button" value="        FINAL FRONTIER        " onclick="type_key('  ')" />
700</td>
701</tr>
702</table>
703</form>
704</body>
705</html>
706"""
707
708LOGIN_HTML="""<html>
709<head>
710<title>Shell Login</title>
711<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
712<style type=text/css>
713a {color: #9f9; text-decoration: none}
714a:hover {color: #0f0}
715hr {color: #0f0}
716html,body,textarea,input,form
717{
718font-family: "Courier New", Courier, mono;
719font-size: 8pt;
720color: #0c0;
721background-color: #020;
722margin:3;
723padding:0;
724border:0;
725}
726input { background-color: #010; }
727input,textarea {
728border-width:1;
729border-style:solid;
730border-color:#0c0;
731padding:3;
732margin:3;
733}
734</style>
735<script language="JavaScript">
736function init ()
737{
738    document.login_form["username"].focus();
739}
740</script>
741</head>
742<body onload="init()">
743<form name="login_form" method="POST">
744<input name="start_server" value="1" type="hidden">
745<input name="sid" value="%(SID)s" type="hidden">
746username: <input name="username" type="text" size="30"><br>
747password: <input name="password" type="password" size="30"><br>
748<input name="submit" type="submit" value="enter">
749</form>
750<br>
751</body>
752</html>
753"""
754
755if __name__ == "__main__":
756    try:
757        main()
758    except Exception, e:
759        print str(e)
760        tb_dump = traceback.format_exc()
761        print str(tb_dump)
762
763