topical media & game development

talk show tell print

lib-js-terminal-sample-remote-terminal.htm / htm



  <html>
  <head>
          <title>termlib Remote Terminal</title>
          <script language="JavaScript" type="text/javascript" src="lib-js-terminal-termlib.js"></script>
          <script language="JavaScript" type="text/javascript" src="lib-js-terminal-termlib-socket.js"></script>
  
  <script type="text/javascript">
  <!--
  
  // *** remote terminal sample ***
  // mass:werk, N.Landsteiner 2007
  
  // path to server script goes here
  var remotePath = '/cgi-bin/termbackend.pl';
  
  // a var to stor the last transfered content
  var lastResponse = '';
  
  var term;
  
  var help = [
          '%+r **** remote terminal sample **** %-r',
          ' ',
          '* use "exit" to quit.',
          '* use "rlogin <username>" for remote login.',
          '* use "rlogout" to clear any rlogin data.',
          '* use "clear" to clear the screen.',
          '* use "help" to see this page.',
          '* any other command will be executed on the remote host.',
          ' '
  ];
  
  function termOpen() {
          if ((!term) || (term.closed)) {
                  term = new Terminal(
                          {
                                  x: 220,
                                  y: 70,
                                  termDiv: 'termDiv',
                                  bgColor: '#232e45',
                                  greeting: help.join('\n'),
                                  handler: termHandler,
                                  exitHandler: termExitHandler
                          }
                  );
                  // set up some instance properties for server-client communication
                  // we use the private namespace "env" of our Terminal instance
                  term.env.myHistPtr = 0;
                  term.env.dir = '';
                  term.env.userid = '';
                  term.env.password = '';
                  // and open the terminal
                  term.open();
                  
                  // some UI tasks
                  var lastResponseLink = (document.getElementById)?
                          document.getElementById('lastResponseLink') : document.all.lastResponseLink;
                  if (lastResponseLink) lastResponseLink.style.display = 'inline';
                  var mainPane = (document.getElementById)?
                          document.getElementById('mainPane') : document.all.mainPane;
                  if (mainPane) mainPane.className = 'lh15 dimmed';
          }
  }
  
  function termExitHandler() {
          // reset the UI
          var lastResponseLink = (document.getElementById)?
                  document.getElementById('lastResponseLink') : document.all.lastResponseLink;
          if (lastResponseLink) lastResponseLink.style.display = 'none';
          var mainPane = (document.getElementById)?
                  document.getElementById('mainPane') : document.all.mainPane;
          if (mainPane) mainPane.className = 'lh15';
  }
  
  // terminal main loop
  
  function termHandler() {
          this.newLine();
  
          // check for raw mode first
          if (this.rawMode) {
                  this.rawMode = false;
                  if (this.env.getPassword) {
                          // we just recieved the password of "rlogin"
                          // first store password 
                          this.env.password = this.lineBuffer;
                          this.env.getPassword = false;
                          // store the local terminal history position for later
                          term.env.myHistPtr = this.histPtr;
                          // send an initial request to verify and get the working dir
                          this.send(
                                  {
                                          url: remotePath,
                                          method: 'post',
                                          callback: socketCallback,
                                          data: {
                                                  command: 'echo "connection up and ready."',
                                                  dir: '',
                                                  user: this.env.userid,
                                                  pass: this.env.password
                                          }
                                  }
                          );
                          // done, leave without prompt (this will come from the callback)
                          return;
                  }
                  // we shouldn't end here
                  this.prompt(); return;
          }
  
          // clear heading white space from input line
          this.lineBuffer = this.lineBuffer.replace(/^\s+/, '');
          
          // evaluate commands (split to words first)
          var argv = this.lineBuffer.split(/\s+/);
          var cmd = argv[0];
          
          switch (cmd) {
                  case 'rlogin':
                          if (argv.length<2 || argv[1]=='') {
                                  this.type('usage: rlogin <username>');
                          }
                          else {
                                  this.env.getPassword = true;
                                  this.env.userid = argv[1];
                                  this.type('Password: ');
                                  // exit in raw mode for password (blind input)
                                  this.rawMode = true;
                                  this.lock = false;
                                  return;
                          }
                          break;
  
                  case 'rlogout':
                          // just forget username and password, reset prompt-string
                          this.env.dir = '';
                          this.env.userid = '';
                          this.env.password = '';
                          this.ps = '>';
                          // reset the local terminal's history to login-state
                          this.histPtr = this.env.myHistPtr;
                          this.history.length = this.histPtr;
                          // clear stored last response
                          lastResponse='';
                          break;
  
                  case 'clear':
                          this.clear();
                          break;
  
                  case 'help':
                          this.clear();
                          this.write(help);
                          break;
  
                  case 'exit':
                          this.env.dir = '';
                          this.env.userid = '';
                          this.env.password = '';
                          this.close();
                          lastResponse='';
                          return;
  
                  default:
                          // no local command
                          if (this.lineBuffer != '') {
                                  if (!this.env.userid) {
                                          this.write('Not logged in, sorry.'); // not logged in
                                  }
                                  else {
                                          // send (unparsed) line to server-backend
                                          this.send(
                                                  {
                                                          url: remotePath,
                                                          method: 'post',
                                                          callback: socketCallback,
                                                          data: {
                                                                  command: this.lineBuffer,
                                                                  dir: this.env.dir,
                                                                  user: this.env.userid,
                                                                  pass: this.env.password
                                                          }
                                                  }
                                          );
                                          // leave without prompt (this will come from the callback)
                                          return;
                                  }
                          }
          }
          this.prompt();
  }
  
  // callback for server-client communication
  
  function socketCallback() {
          var response = this.socket;
          if (response.success) {
                  // split the responseText to lines
                  var lines = response.responseText.split('\n');
                  // get the last valid line and check it
                  if (lines.length > 1 && lines[lines.length-1] == '') lines.length--;
                  var lastline = lines[lines.length-1];
                  if (lastline.indexOf('dir:') == 0) {
                          // if last line starts with "dir:", store this as working dir
                          this.env.dir = lastline.substring(4);
                          // shorten output
                          lines.length--;
                          if (lines[lines.length-1] == '') lines.length--;
                          // and set the prompt-string of the terminal
                          this.ps = '[' + this.env.dir + ']';
                  }
                  else if (lines.length == 1 && lastline == 'Sorry.') {
                          // autorization failed, clear login properties
                          this.env.dir = '';
                          this.env.userid = '';
                          this.env.password = '';
                          this.ps = '>';
                          // reset the local terminal's history to login-state
                          this.histPtr = this.env.myHistPtr;
                          this.history.length = this.histPtr;
                  }
                  // escape any '%' markups for write
                  for (var i=0; i<lines.length; i++) lines[i] = lines[i].replace(/%/g, '%%');
                  // write the remaining content to the terminal in more-mode
                  this.write(lines, true);
                  // store it in a var for later use
                  lastResponse = lines.join('\n');
          }
          else {
                  var s = 'Request failed: ' + response.status + ' ' + response.statusText;
                  if (response.errno) s += '\n'+response.errstring;
                  this.write(s);
                  // reset any authorization properties
                  this.env.dir = '';
                  this.env.userid = '';
                  this.env.password = '';
                  this.ps = '>';
                  this.prompt();
          }
  }
  
  // a function to show the last transfered content in a textarea for copy etc
  
  function showLastResponse() {
          if ((!term) || (term.closed)) return;
          var d = (document.getElementById)?
                  document.getElementById('lastResponseDiv') : document.all.lastResponseDiv;
          if (!d) {
                  d = document.createElement('div');
                  var s= '<form method="get" onsubmit="return false;">';
                  s+= '<table border="0" cellspacing="0" cellpadding="12" style="background-color: #808080; border-style: solid; border-width: 1px; border-color: #999 #555 #555 #999;">';
                  s+= '<tr><td style="color: #000; text-align: center;">Last transfered content<\/td><\/tr>';
                  s+= '<tr><td><textarea cols="80" rows="24" name="lastResponseDisplay" id="lastResponseDisplay" style="color: #000; background-color: #fff;"><\/textarea><\/td><\/tr>';
                  s+= '<tr><td style="color: #000; text-align: right;"><input type="button" value="close" onclick="hideLastResponse()"><\/td><\/tr>';
                  s+= '<\/table>';
                  s+= '<\/form>';
                  d.innerHTML = s;
                  d.id = 'lastResponseDiv';
                  d.style.display = 'block';
                  d.style.position = 'absolute';
                  d.style.left = '10px';
                  d.style.top = '10px';
                  d.style.zIndex = 1000;
                  document.body.appendChild(d);
          }
          // lock the terminal's keyHandler in order to release keyboard capture
          TermGlobals.keylock=true;
          // insert the last stored Response
          var f = (document.getElementById)?
                  document.getElementById('lastResponseDisplay') : document.all.lastResponseDisplay;
          if (f) f.value = lastResponse;
  }
  
  function hideLastResponse() {
          var d = (document.getElementById)?
                  document.getElementById('lastResponseDiv') : document.all.lastResponseDiv;
          if (d) {
                  d.style.display = 'none';
                  d.parentNode.removeChild(d);
          }
          // reset the terminal's keyboard lock
          TermGlobals.keylock = false;
  }
  
  //-->
  </script>
  
  <style type="text/css">
  body,p,a,td {
          font-family: courier,fixed,swiss,sans-serif;
          font-size: 12px;
          color: #cccccc;
  }
  .lh15 {
          line-height: 15px;
  }
  
  .term {
          font-family: "Courier New",courier,fixed,monospace;
          font-size: 12px;
          color: #94aad6;
          background: none;
          letter-spacing: 1px;
  }
  .term .termReverse {
          color: #232e45;
          background: #95a9d5;
  }
  
  a,a:link,a:visited {
          text-decoration: none;
          color: #77dd11;
  }
  a:hover {
          text-decoration: underline;
          color: #77dd11;
  }
  a:active {
          text-decoration: underline;
          color: #eeeeee;
  }
  
  a.termopen,a.termopen:link,a.termopen:visited {
          text-decoration: none;
          color: #77dd11;
          background: none;
  }
  a.termopen:hover {
          text-decoration: none;
          color: #222222;
          background: #77dd11;
  }
  a.termopen:active {
          text-decoration: none;
          color: #222222;
          background: #eeeeee;
  }
  
  table.inventory td {
          padding-bottom: 20px !important;
  }
  
  tt {
          font-family: courier,fixed,monospace;
          color: #ccffaa;
          font-size: 12px;
          line-height: 15px;
  }
  
  .scriptexample {
          font-family: courier,fixed,swiss,sans-serif;
          font-size: 12px;
          color: #222222;
          background-color: #bbbbbb;
          padding: 12px;
  }
  
  .dimmed,.dimmed *,.dimmed * * {
          background-color: #222222 !important;
          color: #333333 !important;
  }
  
  @media print {
          body { background-color: #ffffff; }
          body,p,a,td,li,tt {
                  color: #000000;
          }
          pre,.prop {
                  color: #000000;
          }
          h1 {
                  color: #000000;
          }
          a,a:link,a:visited {
                  color: #000000;
          }
          a:hover {
                  color: #000000;
          }
          a:active {
                  color: #000000;
          }
          table.inventory {
                  display: none;
          }
          .scriptexample {
                  background-color: #eeeeee !important;
                  color: #000000 !important;
          }
  }
  
  </style>
  </head>
  
  <body bgcolor="#222222" link="#77dd11" text="#cccccc" alink="#eeeeee" vlink="#77dd11"
  topmargin="0" bottommargin="0" leftmargin="0" rightmargin="0" marginheight="0" marginwidth="0">
  
  <table border="0" cellspacing="20" cellpadding="0" align="center">
  <tr>
          <td nowrap><a href="lib-js-terminal-index.htm">termlib.js home</a></td>
          <td>|</td>
          <td nowrap><a href="lib-js-terminal-multiterm-test.htm">multiple terminals</a></td>
          <td>|</td>
          <td nowrap><a href="lib-js-terminal-parser-sample.htm">parser</a></td>
          <td>|</td>
          <td nowrap><a href="lib-js-terminal-faq.htm">faq</a></td>
          <td>|</td>
          <td nowrap><a href="lib-js-terminal-readme.txt" title="readme.txt (text/plain)">documentation</a></td>
          <td>|</td>
          <td nowrap><a href="lib-js-terminal-samples.htm" style="color: #cccccc;">samples</a></td>
  </tr>
  </table>
  
  <table border="0" cellspacing="20" cellpadding="0">
          <tr valign="top">
          <td nowrap>
                  <table border="0" cellspacing="0" cellpadding="0" width="190" class="inventory">
                  <tr><td nowrap>
                          <a href="javascript:termOpen()" onfocus="if(this.blur)this.blur();" onmouseover="window.status='open terminal'; return true" onmouseout="window.status=''; return true" class="termopen">&gt; open terminal&nbsp;</a>
                  </td></tr>
                  <tr><td nowrap height="34" valign="bottom">
                          <a href="javascript:showLastResponse()" onfocus="if(this.blur)this.blur();" onmouseover="window.status='show last transfer document'; return true" onmouseout="window.status=''; return true" class="termopen" id="lastResponseLink" style="display: none;">&gt; show last response&nbsp;</a>
                  </td></tr>
                  <tr><td nowrap>
                          &nbsp;
                  </td></tr>
                  <tr><td nowrap class="lh15">
                          &nbsp;<br>
                          remote terminal sample<br>
                          (c) mass:werk,<br>N. Landsteiner 2005-2007<br>
                          <a href="http://www.masswerk.at/" target="_blank">http://www.masswerk.at>
                  </td></tr>
                  </table>
          </td>
          <td class="lh15" width="560" id="mainPane">
                  <p><b style="letter-spacing: 1px;">termlib.js Remote Terminal Sample</b><br>&nbsp;</p>
                  
                  <p>This is a simple example for a remote terminal client using termlib.js.</p>
                  <p>Be sure to understand why <span style="letter-spacing: 1px;">NOT</span> to use this for real!</p>
                  
                  <p>&nbsp;<br><b style="letter-spacing: 1px;">The Client Part</b></p>
                  <p>For the client-side part see the source code of this page:</p>
                  
                  <p>Userid and password are transfered in plain text in the post body of each request. Also userid and password are stored in instance properties of the Terminal object. (Use the local command &quot;rlogout&quot; to clear those.)<br>For minimum security username and password settings are not stored in the script but must be checked on the server side.</p>
                  
                  <p>When logged in, the terminal echoes any output of the shell as it executes the command(s) at the remote host. Have a look at the Perl script below for an example of how to capture both <tt title="STDOUT: Standard Output">STDOUT</tt> and <tt title="STDERR: Standard Error">STDERR</tt> of a command.</p>
                  
                  <p>You will <span style="letter-spacing: 1px;">NOT</span> be able to edit any file at the remote host! For this you would need a true interactive terminal client. (A work-around could be the implementation of a vi-like editor inside the terminal or an export/import facillity for external editing. So you could fetch a file, edit it in the browser, and transfer it back to the remote host.) For the same reason you won't be able to execute any interactive commands (e.g. &quot;<tt title="switch user">su</tt>&quot;).</p>
                  
                  <p>Be sure to understand that this does <span style="letter-spacing: 1px;">NOT</span> open an interactive session.<br>
                  The location of the current working directory is transfered forth and back between host and client. This enables some real shell-like behavior, but it is essential to understand that each line of command(s) is executed separately at the remote host in a shell session of its own.</p>
                  
                  <p>Userid and password are only provided for (a very weak) secutity. The commands will execute with the <tt title="UID: User ID">UID</tt> and <tt title="GID: Group ID">GID</tt> of the server-side CGI-script.</p>
                  
                  <p>As all client-server transfers are performed via XMLHttpRequests, you may only connect to the originating host of the served page.</p>
                  
                  <p>Please mind that there is no guarantee for the correctness of the displayed or transfered data.<br>
                  The termlib.js interface should work reliably with the printable characters of the US-ASCII charset (but mind that it is provided &quot;as is&quot;). Be sure to understand the risks of charset-translations (as might be involved client-side as well as server-side with any interface of this kind) and the immanent risk of damage and/or loss of data.</p>
                  
                  <p>&nbsp;</p>
                  
                  <p><b style="letter-spacing: 1px;">The Server Part</b></p>
                  
                  <p>This is a simple Perl script as an example for a server-side interface.<br>We'll assume the host is UINIX-style and addressed via the Common Gateway Interface of the web-server (e.g. Apache).
                  As above, be sure to understand why <span style="letter-spacing: 1px;">NOT</span> to use this for real.</p>
                  
  
  <pre class="scriptexample">#!/usr/bin/perl
  
  
termbackend.pl


sample backend for the termlib.js remote terminal sample


example only, do not use for real!



  use strict;
  use CGI;
  use IPC::Open3;
  use Symbol qw(gensym);
  
    
username and password here

my userid = 'user'; my password = 'password';
get cgi params

my q = new CGI; my user = q-&gt;param('user'); my pass = q-&gt;param('pass'); my cmd = q-&gt;param('command') || ''; my dir = q-&gt;param('dir') || ENV{DOCUMENT_ROOT};
start output

print &quot;Content-type: text/plain\n\n&quot;; if ((user eq userid) && (pass eq password)) { # passed user authorization # prepare the command string: # first append echo of working dir as last line # and clean up the command string (leading blanks etc) cmd .= ';echo &quot;&quot;;echo &quot;dir:PWD&quot;'; cmd =~ s/^[\\;\s]+//; # change dir and return command output ... chdir(dir) || die &quot;Error: Could not change directory.&quot;; # simple backticks (print `cmd`) won't catch STDERR # so we'll use open3 and mix STDOUT and STDERR ... my pid = open3(gensym, \*CATCH, \*CATCH, cmd); while( &lt;CATCH&gt; ) { print _; } waitpid(pid, 0); } else { # authorization failed, return Sorry. print &quot;Sorry.&quot;; }
eof</pre>

          <p>&nbsp;</p>
          <p>A script like this must be installed on your web-server (probably in the cgi-bin directory) with sufficient execute permissions in order to make this sample work. Since this is not installed on the <a href="http://www.masswerk.at/" target="_blank">mass:werk</a> server, this page won't be functional on this site.</p>
          <p>Be sure to understand that installing a script like this on a public server will mean to welcome hackers with open doors ...</p>
          <p>&nbsp;</p>
  
          <p>Disclaimer<br>
          This piece of software is published as an example only and AS IS. No warranty of any kind is granted, not even the implied warranty of fitness for a particular purpose. You are advised to <span style="letter-spacing: 1px;">NOT</span> use this script for real. Be aware that using this script or any interface alike bears the risk of serious damage and loss of data. Be also aware that there is no guarantee of any kind for the correctness of the data displayed or transfered.</p>
                  
          </td>
          </tr>
  </table>
  
  <div id="termDiv" style="position:absolute; visibility: hidden; z-index:1;"></div>
  
  </body>
  </html>
  


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.