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 <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