Home | History | Annotate | Download | only in examples
      1 #!/usr/bin/python
      2 ##!/usr/bin/env python
      3 """CGI shell server
      4 
      5 This exposes a shell terminal on a web page.
      6 It uses AJAX to send keys and receive screen updates.
      7 The 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 
     15 This project is probably not the most security concious thing I've ever built.
     16 This should be considered an experimental tool -- at best.
     17 """
     18 import sys,os
     19 sys.path.insert (0,os.getcwd()) # let local modules precede any installed modules
     20 import socket, random, string, traceback, cgi, time, getopt, getpass, threading, resource, signal
     21 import pxssh, pexpect, ANSI
     22 
     23 def exit_with_usage(exit_code=1):
     24     print globals()['__doc__']
     25     os._exit(exit_code)
     26 
     27 def 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 
     43 def 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 
    125 class 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 
    143 def 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 
    154 def 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 
    228 def 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 
    289 def 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 
    330 def random_sid ():
    331     a=random.randint(0,65535)
    332     b=random.randint(0,65535)
    333     return '%04x%04x.sid' % (a,b)
    334 
    335 def 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      
    351 def 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 
    357 def 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.
    364 CGISH_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>
    370 a {color: #9f9; text-decoration: none}
    371 a:hover {color: #0f0}
    372 hr {color: #0f0}
    373 html,body,textarea,input,form
    374 {
    375 font-family: "Courier New", Courier, mono; 
    376 font-size: 8pt; 
    377 color: #0c0;
    378 background-color: #020;
    379 margin:0;
    380 padding:0;
    381 border:0;
    382 }
    383 input { background-color: #010; }
    384 textarea {
    385 border-width:1;
    386 border-style:solid;
    387 border-color:#0c0;
    388 padding:3;
    389 margin:3;
    390 }
    391 </style>
    392 
    393 <script language="JavaScript">
    394 function focus_first()
    395 {if (document.forms.length > 0)
    396 {var TForm = document.forms[0];
    397 for (i=0;i<TForm.length;i++){
    398 if ((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 (at] noah.org>
    406 var flag_shift=0;
    407 var flag_shiftlock=0;
    408 var flag_ctrl=0;
    409 var ButtonOnColor="#ee0";
    410 
    411 function 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 }
    419 function get_password ()
    420 {
    421     var username = prompt("username?","");
    422     var password = prompt("password?","");
    423     start_server (username, password);
    424 }
    425 function 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 }
    453 function 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 }
    462 function 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 }
    471 function 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 //}
    481 function refresh_screen()
    482 {
    483     load_url_to_screen('cgishell.cgi?ajax=refresh&sid=%(SID)s');
    484 }
    485 function query_hash()
    486 {
    487     load_url_to_screen('cgishell.cgi?ajax=hash&sid=%(SID)s');
    488 }
    489 function query_cursor()
    490 {
    491     load_url_to_screen('cgishell.cgi?ajax=cursor&sid=%(SID)s');
    492 }
    493 function exit_server()
    494 {
    495     load_url_to_screen('cgishell.cgi?ajax=exit&sid=%(SID)s');
    496 }
    497 function 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 
    521 function 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 
    536 function 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 }
    550 function 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 }
    565 function 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 }
    597 function 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;       
    613 function 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 
    708 LOGIN_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>
    713 a {color: #9f9; text-decoration: none}
    714 a:hover {color: #0f0}
    715 hr {color: #0f0}
    716 html,body,textarea,input,form
    717 {
    718 font-family: "Courier New", Courier, mono;
    719 font-size: 8pt;
    720 color: #0c0;
    721 background-color: #020;
    722 margin:3;
    723 padding:0;
    724 border:0;
    725 }
    726 input { background-color: #010; }
    727 input,textarea {
    728 border-width:1;
    729 border-style:solid;
    730 border-color:#0c0;
    731 padding:3;
    732 margin:3;
    733 }
    734 </style>
    735 <script language="JavaScript">
    736 function 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">
    746 username: <input name="username" type="text" size="30"><br>
    747 password: <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 
    755 if __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