README 000644 000423 000000 00000004161 11464704262 012204 0 ustar 00luigi wheel 000000 000000 kiterm -- browser-based terminal for the kindle
This package is a simplified and stripped down port of the AjaxTerm
program that is suitable for operation on the Kindle.
AjaxTerm uses some simple javascript on the client to access a
server in charge of forking shells (and keeping them alive)
and pass tty data across the HTTP connection.
This approach is especially useful on the kindle, where
running an xterm or a shell in console mode is challenging.
Instead, the browser can be used for this.
The original AjaxTerm uses a Python script, with the usual amount
of classes, to implement its services.
I have completely reimplemented the program in C, using a
single process that handles multiple connections and forks
the child shells as desired.
The javascript has also been heavily simplified, removing
unnecessary libraries.
The tarball includes:
+ this README
+ myts.c (source code for the server)
+ myts.arm -- a version of the server compiled for the kindle
+ ajaxterm.{html,js,css}, html files
To use the program you must run the server from the
directory where the javascript files are, and then
launch a browser to
http://localhost:8022/
On the kindle, ALT maps to CTRL, and the ESC key is the
"page back" key on the left of the screen.
TODO:
At this stage the program is still a bit experimental in that it
still needs work on two areas, namely process management
and terminal emulation. In particular:
+ complete ANSI control code emulation
not all ANSI sequences are recognised, though
most things work (top, vi, ...). The missing sequences
mostly refer to color handling.
+ reconnect to existing sessions
at the moment each session uses a random key so
if the browser dies or disconnects, you cannot
reach the existing session anymore
+ session termination
shells remain alive until the server terminates.
I should implement a way to explicitly kill
a session or connect to one of the existing ones.
=== References ===
http://antony.lesuisse.org/software/ajaxterm/
wget http://antony.lesuisse.org/ajaxterm/files/Ajaxterm-0.10.tar.gz
tar zxvf Ajaxterm-0.10.tar.gz
cd Ajaxterm-0.10
./ajaxterm.py
ajaxterm.css 000644 000423 000000 00000003242 11464701237 013647 0 ustar 00luigi wheel 000000 000000 /*
* $Id: ajaxterm.css 7656 2010-11-05 04:21:40Z luigi $
* default colors for kindle
*/
.kindle { background-color: white; color: black; }
pre.stat {
margin: 0px;
padding: 4px;
display: block;
font-family: monospace;
white-space: pre;
background-color: black;
border-top: 1px solid black;
color: white;
}
pre.stat span {
padding: 0px;
}
pre.stat .on {
background-color: #080;
font-weight: bold;
color: white;
cursor: pointer;
}
pre.stat .off {
background-color: #888;
font-weight: bold;
color: white;
cursor: pointer;
}
pre.term {
margin: 0px;
padding: 4px;
display: block;
font-family: monospace;
white-space: pre;
background-color: #fff;
border-top: 1px solid white;
color: #000;
}
pre.term span.f0 { color: #000; }
pre.term span.f1 { color: #b00; }
pre.term span.f2 { color: #0b0; }
pre.term span.f3 { color: #bb0; }
pre.term span.f4 { color: #00b; }
pre.term span.f5 { color: #b0b; }
pre.term span.f6 { color: #0bb; }
pre.term span.f7 { color: #bbb; }
pre.term span.f8 { color: #666; }
pre.term span.f9 { color: #f00; }
pre.term span.f10 { color: #0f0; }
pre.term span.f11 { color: #ff0; }
pre.term span.f12 { color: #00f; }
pre.term span.f13 { color: #f0f; }
pre.term span.f14 { color: #0ff; }
pre.term span.f15 { color: #fff; }
pre.term span.b0 { background-color: #000; }
pre.term span.b1 { background-color: #b00; }
pre.term span.b2 { background-color: #0b0; }
pre.term span.b3 { background-color: #bb0; }
pre.term span.b4 { background-color: #00b; }
pre.term span.b5 { background-color: #b0b; }
pre.term span.b6 { background-color: #0bb; }
pre.term span.b7 { background-color: #bbb; }
body { background-color: #888; }
#term {
float: left;
}
ajaxterm.html 000644 000423 000000 00000001075 11464703015 014021 0 ustar 00luigi wheel 000000 000000
Ajaxterm - Luigi Rizzo version
ajaxterm.js 000644 000423 000000 00000027264 11464703243 013504 0 ustar 00luigi wheel 000000 000000 /*
* $Id: ajaxterm.js 7657 2010-11-05 04:38:48Z luigi $
*
* setHTML works around an IE bug that requires content to be installed twice
* (and still without working handlers)
*/
function setHTML(el, t) { if (!el) return; el.innerHTML = t; el.innerHTML = el.innerHTML; }
ajaxterm={};
ajaxterm.Terminal_ctor=function(id,width,height, keylen) {
if (!width) width=80;
if (!height) height=25;
if (!keylen) keylen=16;
var ie=(window.ActiveXObject) ? 1 : 0;
var webkit= (navigator.userAgent.indexOf("WebKit") >= 0) ? 1 : 0;
var sid="";
var alphabet = 'abcdefghijklmnopqrstuvwxyz';
var l = alphabet.length;
for (var i=0; i < keylen; i++) { // generate a session key
sid += alphabet.charAt(Math.round(Math.random()*l));
}
var query0="s="+sid+"&w="+width+"&h="+height;
var query1=query0+"&c=1&k=";
var buf="";
var timeout;
var error_timeout;
var keybuf=[];
var sending=0;
var rmax=1;
/* elements in the top bar */
var div=document.getElementById(id);
var dstat=document.createElement('pre');
var opt_get=document.createElement('a');
var opt_color=document.createElement('a');
var opt_paste=document.createElement('a');
var sdebug=document.createElement('span');
var dterm=document.createElement('div');
function debug(s) { setHTML(sdebug, s); }
function error() {
debug("Connection lost timeout ts:"+((new Date).getTime()));
}
function opt_add(opt,name) {
opt.className='off';
setHTML(opt, ' '+name+' ');
dstat.appendChild(opt);
dstat.appendChild(document.createTextNode(' '));
}
function do_get(event) { /* toggle get/post */
opt_get.className=(opt_get.className=='off')?'on':'off';
debug('GET '+opt_get.className);
}
function do_color(event) {
var o=opt_color.className=(opt_color.className=='off')?'on':'off';
query1 = query0 + (o=='on' ? "&c=1" : "") + "&k=";
debug('Color '+opt_color.className);
}
function mozilla_clipboard() {
// mozilla sucks
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (err) {
debug('Access denied, more info');
return undefined;
}
var clip = Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard);
var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
if (!clip || !trans) {
return undefined;
}
trans.addDataFlavor("text/unicode");
clip.getData(trans,clip.kGlobalClipboard);
var str=new Object();
var strLength=new Object();
try {
trans.getTransferData("text/unicode",str,strLength);
} catch(err) {
return "";
}
if (str) {
str=str.value.QueryInterface(Components.interfaces.nsISupportsString);
}
return (str) ? str.data.substring(0,strLength.value / 2) : "";
}
function do_paste(event) { /* not always working */
var p=undefined;
if (window.clipboardData) {
p=window.clipboardData.getData("Text");
} else if(window.netscape) {
p=mozilla_clipboard();
} else {
return; // failed
}
debug('Pasted');
queue(encodeURIComponent(p));
}
function update() {
if (sending) return;
sending=1;
var r=new XMLHttpRequest();
var send="";
while (keybuf.length>0) {
send+=keybuf.pop();
}
var query=query1+send;
if (opt_get.className=='on') {
r.open("GET","u?"+query,true);
if (ie) { // force a refresh
r.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
}
} else {
r.open("POST","u",true);
}
r.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
r.onreadystatechange = function () {
if (r.readyState!=4) return;
window.clearTimeout(error_timeout);
if (r.status!=200) {
debug("Connection error, status: "+r.status + ' ' + r.statusText);
return;
}
if(ie) {
var responseXMLdoc = new ActiveXObject("Microsoft.XMLDOM");
responseXMLdoc.loadXML(r.responseText);
de = responseXMLdoc.documentElement;
} else {
de=r.responseXML.documentElement;
}
if (de.tagName=="pre") {
setHTML(dterm, unescape(r.responseText));
rmax=100;
} else {
rmax*=2;
if(rmax>2000)
rmax=2000;
}
sending=0;
timeout=window.setTimeout(update,rmax);
}
error_timeout=window.setTimeout(error,5000);
r.send ( (opt_get.className=='on') ? null : query );
}
function queue(s) {
keybuf.unshift(s);
if (sending==0) {
window.clearTimeout(timeout);
timeout=window.setTimeout(update,1);
}
}
function keypress(ev) {
if (!ev) var ev=window.event;
s="kp kC="+ev.keyCode+" w="+ev.which +
" sh="+ev.shiftKey+" ct="+ev.ctrlKey+" al="+ev.altKey;
debug(s);
var kc;
var k="";
if (ev.keyCode)
kc=ev.keyCode;
if (ev.which)
kc=ev.which;
if (0 && ev.altKey) { /* ESC + char */
if (kc>=65 && kc<=90)
kc+=32;
if (kc>=97 && kc<=122)
k=String.fromCharCode(27)+String.fromCharCode(kc);
} else if (ev.ctrlKey || ev.altKey) {
if (kc>=65 && kc<=90)
k=String.fromCharCode(kc-64); // Ctrl-A..Z
else if (kc>=97 && kc<=122)
k=String.fromCharCode(kc-96); // Ctrl-A..Z
else if (kc==54) k=String.fromCharCode(30); // Ctrl-^
else if (kc==109) k=String.fromCharCode(31); // Ctrl-_
else if (kc==219) k=String.fromCharCode(27); // Ctrl-[
else if (kc==220) k=String.fromCharCode(28); // Ctrl-\
else if (kc==221) k=String.fromCharCode(29); // Ctrl-]
else if (kc==219) k=String.fromCharCode(29); // Ctrl-]
else if (kc==219) k=String.fromCharCode(0); // Ctrl-@
} else if (ev.which==0) {
if (kc==9) k=String.fromCharCode(9); // Tab
else if (kc==8) k=String.fromCharCode(127); // Backspace
else if (kc==27) k=String.fromCharCode(27); // Escape
else {
if (kc==33) k="[5~"; // PgUp
else if (kc==34) k="[6~"; // PgDn
else if (kc==35) k="[4~"; // End
else if (kc==36) k="[1~"; // Home
else if (kc==37) k="[D"; // Left
else if (kc==38) k="[A"; // Up
else if (kc==39) k="[C"; // Right
else if (kc==40) k="[B"; // Down
else if (kc==45) k="[2~"; // Ins
else if (kc==46) k="[3~"; // Del
else if (kc==112) k="[[A"; // F1
else if (kc==113) k="[[B"; // F2
else if (kc==114) k="[[C"; // F3
else if (kc==115) k="[[D"; // F4
else if (kc==116) k="[[E"; // F5
else if (kc==117) k="[17~"; // F6
else if (kc==118) k="[18~"; // F7
else if (kc==119) k="[19~"; // F8
else if (kc==120) k="[20~"; // F9
else if (kc==121) k="[21~"; // F10
else if (kc==122) k="[23~"; // F11
else if (kc==123) k="[24~"; // F12
if (k.length)
k=String.fromCharCode(27)+k;
}
} else {
if (kc==8)
k=String.fromCharCode(127); // Backspace
else
k=String.fromCharCode(kc);
}
if (k.length)
queue( (k=="+") ? "%2B" : utf8Escape(k) );
ev.cancelBubble=true;
if (ev.stopPropagation) ev.stopPropagation();
if (ev.preventDefault) ev.preventDefault();
return false;
}
function keydown(ev) {
if (!ev) var ev=window.event;
if (ie || webkit) {
s="kd kC="+ev.keyCode+" w="+ev.which+" sh=" +
ev.shiftKey+" ct="+ev.ctrlKey+" al="+ev.altKey;
debug(s);
o={9:1,8:1,27:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1,45:1,46:1,112:1,
113:1,114:1,115:1,116:1,117:1,118:1,119:1,120:1,121:1,122:1,123:1};
if (o[ev.keyCode] || ev.ctrlKey || ev.altKey) {
ev.which=0;
return keypress(ev);
}
}
}
function keyup(ev) {
if (!ev) var ev=window.event;
if (ie || webkit) {
s="ku kC="+ev.keyCode+" w="+ev.which+" sh=" +
ev.shiftKey+" ct="+ev.ctrlKey+" al="+ev.altKey;
debug(s);
if (ev.keyCode == 33) queue("%1B");
return false;
o={9:1,8:1,27:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1,45:1,46:1,112:1,
113:1,114:1,115:1,116:1,117:1,118:1,119:1,120:1,121:1,122:1,123:1};
return;
if (o[ev.keyCode] || ev.ctrlKey || ev.altKey) {
ev.which=0;
return keypress(ev);
}
}
}
function init() {
dstat.appendChild(document.createTextNode(' '));
opt_add(opt_color,'Colors');
opt_color.className='on';
opt_color.title='Toggle color or grey';
opt_add(opt_get,'GET');
opt_get.title = 'Toggle GET or POST methods';
opt_add(opt_paste,'Paste');
opt_paste.title = 'Paste from clipboard';
dstat.appendChild(sdebug);
dstat.className='stat';
div.appendChild(dstat);
div.appendChild(dterm);
if(opt_color.addEventListener) {
opt_get.addEventListener('click',do_get,true);
opt_color.addEventListener('click',do_color,true);
opt_paste.addEventListener('click',do_paste,true);
} else {
opt_get.attachEvent("onclick", do_get);
opt_color.attachEvent("onclick", do_color);
opt_paste.attachEvent("onclick", do_paste);
}
document.onkeypress=keypress;
document.onkeydown=keydown;
document.onkeyup=keyup;
timeout=window.setTimeout(update,100);
}
init();
debug('Session: ' + sid);
}
ajaxterm.Terminal=function(id,width,height, keylen) {
return new this.Terminal_ctor(id,width,height, keylen);
}
/* escape to utf8 -- note that this is similar to encodeURIComponent */
var char2hex = new Array(
"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
"%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
"%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
"%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
"%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f",
"%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
"%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f",
"%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
"%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f",
"%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
"%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f",
"%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
"%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f",
"%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
"%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f",
"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
"%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
"%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
"%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
"%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
"%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
"%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
"%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
"%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
"%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
"%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
"%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
"%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
"%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
"%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
);
function utf8Escape(s) {
var ret = "", i, len = s.length;
for (i = 0; i < len; i++) {
var ch = s.charCodeAt(i);
if ( (65 <= ch && ch <= 90) || (97 <= ch && ch <= 122) ||
(48 <= ch && ch <= 57) || ch == 45 || ch == 95 ||
ch == 46 || ch == 33 || ch == 126 || ch == 42 ||
ch == 39 || ch == 40 || ch == 41) {
ret += String.fromCharCode(ch);
} else if (ch == 32) {
ret += '+';
} else if (ch <= 0x007F) {
ret += char2hex[ch];
} else if (ch <= 0x07FF) {
ret += char2hex[0xc0 | (ch >> 6)];
ret += char2hex[0x80 | (ch & 0x3F)];
} else {
ret += char2hex[0xe0 | (ch >> 12)];
ret += char2hex[0x80 | ((ch >> 6) & 0x3F)];
ret += char2hex[0x80 | (ch & 0x3F)];
}
}
return ret;
}
myts.c 000644 000423 000000 00000053476 11464703243 012477 0 ustar 00luigi wheel 000000 000000 /*
Copyright (c) 2010 Luigi Rizzo. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
/*
* $Id: myts.c 7657 2010-11-05 04:38:48Z luigi $
Backend for ajaxterm
The main instance of a program keeps a list of current sessions,
and for each of them handles a child which runs a shell and
talks through a pty pair.
In standalone mode, the process runs as a web server and so it
can suspend requests for some time until they are handled.
In slave1 mode, it talks through a single unix pipe so it cannot suspend.
In slave2 mode it uses multiple unix pipes.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef linux
#include /* strcasestr */
/* strcasestr prototype is problematic */
char *strcasestr(const char *haystack, const char *pneedle);
#include
#else
#include /* forkpty */
#endif
#include /* gettimeofday */
#include
#include
#include /* PROT_READ and mmap */
#include
#include /* gethostbyname */
#include /* isalnum */
#include /* inet_aton */
#define KMAX 256 /* keyboard queue */
#define SMAX 256 /* keyboard queue */
#define ROWS 25
#define COLS 80
#define INBUFSZ 4096 /* GET/POST queries */
#define OUTBUFSZ (5*ROWS*COLS) /* output buffer */
/* supported mime types -- suffix space mime. Default is text/plain.
* processed by getmime(filename)
*/
static const char *mime_types[] = {
"html text/html",
"htm text/html",
"js text/javascript",
"css text/css",
NULL
};
/*
* struct my_sock contains support for talking to the browser.
* It contains a buffer for receiving the incoming request,
* hold a copy of the response, and support for an mmapped file.
* Initially, reply = 0, len = sizeof(inbuf) and pos = 0,
* we accumulate data into inbuf and call parse_msg() to tell
* whether we are done.
* During the reply phase, inbuf contains the response hdr,
* possibly map contains the body, len = header length, body_len = body_len
* and pos = 0. We first send the header, then toggle len = -body_len,
* and then send the body. When done, detach the buffer.
* If filep is set, then map is a mapped file, otherwise points to
* some other buffer and does not need to be freed.
*/
struct my_sock {
struct my_sock *next;
int socket; /* the network socket */
int reply; /* set when replying */
int pos, len; /* read position and length */
struct sockaddr_in sa; /* not used */
char inbuf[INBUFSZ]; /* I/O buffer */
char outbuf[OUTBUFSZ]; /* I/O buffer */
/* memory mapped file */
int filep;
int body_len;
char *map;
};
/*
* struct my_sess describes a shell session to which we talk.
*/
struct my_sess {
struct my_sess *next;
char *name; /* session name */
int pid; /* pid of the child */
int master; /* master tty */
/* screen/keyboard buf have len *pos. *pos is the next byte to send */
int kseq; // need a sequence number for kb input ?
int klen; /* pending input for keyboard */
char keys[KMAX];
int slen; /* pending input for screen */
char sbuf[SMAX];
int rows, cols; /* geometry */
int cur;
char *page; /* dump of the screen */
char *oldpage; /* dump of the screen */
};
/*
* my_args contains all the arguments for the program
*/
struct my_args {
struct sockaddr_in sa;
char *cmd; /* command to run */
int nsess; /* number of sessions */
int lfd; /* listener fd */
struct my_sock *socks;
struct my_sess *sess;
int cycles;
int unsafe; /* allow read all file systems */
int verbose; /* allow read all file systems */
};
int myerr(const char *s)
{
fprintf(stderr, "error: %s\n", s);
exit(2);
}
/* convert the html encoding back to plain ascii
* XXX must be fixed to handle UTF8
*/
char *unescape(char *s)
{
char *src, *dst, c, *hex = "0123456789abcdef0123456789ABCDEF";
for (src = dst = s; *src; ) {
c = *src++;
if (c == '+') c = ' ';
else if (c == '%') {
c = '\0';
if (*src && index(hex, *src)) c = c*16 + ((index(hex, *src++)-hex)&0xf);
if (*src && index(hex, *src)) c = c*16 + ((index(hex, *src++)-hex)&0xf);
}
*dst++ = c;
}
*dst++ = '\0';
return s;
}
/*
* append a string to a page, interpreting ANSI sequences
*/
char *page_append(struct my_sess *sh, char *s)
{
int pagelen = sh->rows * sh->cols;
int curcol;
for (; *s; s++) {
char c = *s;
if (sh->cur >= pagelen) {
// fprintf(stderr, "+++ scroll at %d / %d +++\n", sh->cur, pagelen);
sh->cur = pagelen - sh->cols;
memcpy(sh->page, sh->page + sh->cols, sh->cur);
memset(sh->page + pagelen - sh->cols, ' ', sh->cols);
}
curcol = sh->cur % sh->cols;
/* now should map actions */
if (c == '\r') {
sh->cur -= curcol;
} else if (c == '\n') {
sh->cur += sh->cols;
if (sh->cur >= pagelen) { // XXX not sure if needed
// fprintf(stderr, "+++ scroll2 at %d / %d +++\n", sh->cur, pagelen);
sh->cur -= sh->cols;
memcpy(sh->page, sh->page + sh->cols, sh->cur);
memset(sh->page + pagelen - sh->cols, ' ', sh->cols);
}
} else if (c == '\t') {
if (curcol >= pagelen - 8)
sh->cur += (sh->cols - 1 - curcol);
else
sh->cur += 8 - (sh->cur % 8);
} else if (c == '\b') { // backspace
if (curcol > 0)
sh->cur--;
sh->page[sh->cur] = ' ';
} else if (c == '\033') { /* escape */
if (!s[1])
break; // process later
if (s[1] == '[' ) { // CSI found
/* see http://en.wikipedia.org/wiki/ANSI_escape_code */
char *parm, *base = s + 2, cmd, mark=' ';
int a1= 1, a2= 1, a3 = 1;
// fprintf(stderr, "+++ CSI FOUND ESC-%s\n", s+1);
if (*base == '?')
mark = *base++;
if (!*base)
break; // process later
// skip parameters
for (parm = base; *parm && index("0123456789;", *parm); parm++) ;
// fprintf(stderr, "+++ now PARM %s\n", parm);
cmd = parm[0];
if (!cmd)
return s; // process later
s = parm;
sscanf(base, "%d;%d;%d", &a1, &a2, &a3);
if (cmd == 'A') { // up
while (a1-- > 0) {
if (sh->cur >= sh->cols) sh->cur -= sh->cols;
}
} else if (cmd == 'B') { // down
while (a1-- > 0) {
if (sh->cur < pagelen -sh->cols) sh->cur += sh->cols;
}
} else if (cmd == 'C') { // right
if (a1 >= sh->cols - curcol) a1 = sh->cols - curcol - 1;
sh->cur += a1;
} else if (cmd == 'D') { // left
if (a1 > curcol) a1 = curcol;
sh->cur -= a1;
} else if (cmd == 'H' || cmd == 'f') { // position
if (a1 > sh->rows) a1 = sh->rows;
if (a2 > sh->cols) a2 = sh->cols;
sh->cur = (a1 - 1)*sh->cols + a2 - 1;
} else if (cmd == 'J') { /* clear part of screen */
if (base == parm || a1 == 0) {
a1 = pagelen - sh->cols;
memset(sh->page + sh->cur, ' ', a1);
} else if (a1 == 1) {
memset(sh->page, ' ', sh->cur);
} else if (a1 == 2) {
memset(sh->page, ' ', pagelen);
sh->cur = 0; // msdos ansy.sys
} else {
goto notfound;
}
} else if (cmd == 'K') { /* clear */
if (base == parm || a1 == 0) {
a1 = sh->cols - curcol;
memset(sh->page + sh->cur, ' ', a1);
} else if (a1 == 1) {
goto notfound;
} else if (a1 == 2) {
goto notfound;
} else {
goto notfound;
}
} else if (mark == '?' && cmd == 'l') { /* hide cursor */
goto notfound;
} else if (mark == '?' && cmd == 'h') { /* show cursor */
goto notfound;
} else {
notfound:
fprintf(stderr, "ANSI sequence %d %d %d ( ESC-[%c%.*s)\n",
a1, a2, a3, mark, (parm+1 - base), base);
}
}
} else {
sh->page[sh->cur] = *s;
if (curcol != sh->cols -1) sh->cur++;
}
if (sh->cur >= pagelen)
fprintf(stderr,"--- ouch, overflow on c %d\n", c);
}
if (*s) {
fprintf(stderr, "----- leftover stuff ESC [%s]\n", s+1);
}
return s;
}
int forkchild(struct my_sess *s, char *cmd)
{
struct winsize ws;
bzero(&ws, sizeof(ws));
ws.ws_row = s->rows;
ws.ws_col = s->cols;
s->pid = forkpty(&s->master, NULL, NULL, &ws);
// fprintf(stderr, "forkpty gives pid %d pty %d\n", s->pid, s->master);
if (s->pid < 0) {
fprintf(stderr, "forkpty failed\n");
return 1;
}
if (s->pid == 0) { /* execvp the shell */
char *av[] = { cmd, NULL};
execvp(av[0], av);
exit(1);
}
return 0;
}
int u_mode(struct my_args *me, struct my_sock *ss, char *body)
{
/* ajaxterm parameters */
char *s = NULL, *w = NULL, *h = NULL, *c = NULL, *k = NULL;
char *cur, *p, *p2;
struct timeval t;
char tmp[ROWS*COLS+1];
char *src, *dst;
char done;
int i, l, rows = 0, cols = 0;
struct my_sess *sh = NULL;
for (p = body; (cur = strsep(&p, "&")); ) {
if (!*cur) continue;
p2 = strsep(&cur, "=");
if (!strcmp(p2, "s")) s = cur;
if (!strcmp(p2, "w")) w = cur;
if (!strcmp(p2, "h")) h = cur;
if (!strcmp(p2, "c")) c = cur;
if (!strcmp(p2, "k")) k = cur;
}
if (!s || !*s)
goto error;
if (w) cols = atoi(w);
if (cols < 10 || cols > 150) cols = 80;
if (h) rows = atoi(h);
if (rows < 4 || rows > 80) rows = 25;
for (i = 0, sh = me->sess; sh; sh = sh->next, i++) {
// fprintf(stderr, "at %p have %s\n", sh, sh->name);
if (!strcmp(s, sh->name))
break;
}
if (!sh) {
// fprintf(stderr, "--- session %s not found %d\n", s, i);
int l1 = rows * cols + 1;
int l2 = strlen(s) + 1;
int pagelen = rows*cols;
sh = calloc(1, sizeof(*sh) + l1*2 + l2);
if (!sh)
goto error;
sh->rows = rows;
sh->cols = cols;
sh->cur = 0;
sh->name = (char *)(sh + 1);
sh->page = sh->name + l2;
sh->oldpage = sh->page + l1;
memset(sh->page, ' ', pagelen);
strcpy(sh->name, s);
sh->next = me->sess;
me->sess = sh;
if (forkchild(sh, me->cmd)) {
ss->len = sprintf(ss->outbuf,
"HTTP/1.1 400 fork failed\r\n\r\n");
return 0;
}
}
unescape(k);
strncat(sh->keys + sh->klen, k, sizeof(sh->keys) - 1 - sh->klen);
sh->klen = strlen(sh->keys);
rows = sh->rows;
cols = sh->cols;
src = sh->page;
sh->page[rows*cols] = '\0'; // ensure it is terminated XXX bug in cursor handling
if (!strcmp(sh->page, sh->oldpage)) {
/* no modifications, compact version */
same:
ss->len = sprintf(ss->outbuf,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/xml\r\n\r\n"
""
"");
if (me->verbose) fprintf(stderr, "response %s\n", ss->outbuf);
return 0;
} else {
strcpy(sh->oldpage, sh->page);
}
goto good;
error:
goto same;
rows = ROWS;
cols = COLS;
gettimeofday(&t, NULL);
l = sprintf(tmp, "session %p at %d.%06d pressed %s",
ss, (int)t.tv_sec, (int)(t.tv_usec), k);
src = tmp;
good:
ss->len = sprintf(ss->outbuf,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/xml\r\n\r\n"
""
"
");
done = '\0';
for (i=0, dst = ss->outbuf + ss->len; i < rows*cols;) {
char cc = done ? done : src[i];
if (!cc) done = cc = ' ';
if (sh && src == sh->page && i == sh->cur)
dst += sprintf(dst, "");
if (isalnum(cc) || cc == ' ') // XXX
*dst++ = cc;
else
dst += sprintf(dst, "%%%02x", cc);
if (sh && src == sh->page && i == sh->cur)
dst += sprintf(dst, "");
if (++i % cols == 0)
*dst++ = '\n';
}
l = sprintf(dst, "
");
ss->len = dst + l - ss->outbuf;
if (me->verbose) fprintf(stderr, "response %s\n", ss->outbuf);
return 0;
}
/*
* HTTP support
*/
int opensock(struct sockaddr_in sa)
{
int fd;
int i;
fd = socket(sa.sin_family, SOCK_STREAM, 0);
if (fd < 0) {
perror(" cannot create socket");
return -1;
}
fcntl(fd, F_SETFD, 1 ); // close on exec
i = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i) ) < 0 ) {
perror(" cannot reuseaddr");
return -1;
}
if (bind(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in))) {
perror( "bind" );
return -1;
}
if (listen(fd, 20) < 0 ) {
perror( "listen" );
return -1;
}
return fd;
}
int handle_listen(struct my_args *me)
{
int fd;
unsigned int l;
struct sockaddr_in sa;
struct my_sock *s;
if (me->verbose) fprintf(stderr, "listening socket\n");
bzero(&sa, sizeof(sa));
l = sizeof(sa);
fd = accept(me->lfd, (struct sockaddr *)&sa, &l);
if (fd < 0) {
fprintf(stderr, "listen failed\n");
return -1;
}
s = calloc(1, sizeof(*s));
if (!s) {
close(fd);
fprintf(stderr, "alloc failed\n");
return -1;
}
s->sa = sa;
s->socket = fd;
s->filep = -1; /* no file */
s->pos = 0;
s->len = sizeof(s->inbuf);
s->next = me->socks;
me->socks = s;
return 0;
}
/* support function to return mime types. */
const char *getmime(const char *res)
{
const char **p, *suffix;
int lres = strlen(res);
res += lres; /* move to the end of the resource */
for (p = mime_types; (suffix = *p); p++) {
int lsuff = strcspn(suffix, " ");
if (lsuff > lres)
continue;
if (!bcmp(res - lsuff, suffix, lsuff))
return suffix + lsuff + 1;
}
return "text/plain"; /* default */
}
/*
* A stripped down parser for http, which also interprets what
* we need to do.
*/
int parse_msg(struct my_args *me, struct my_sock *s)
{
char *a, *b, *c = s->inbuf; /* strsep support */
char *body, *method = NULL, *resource = NULL;
int row, tok;
int clen = -1;
char *err = "generic error";
if (s->pos == s->len) { // buffer full, we are done
fprintf(stderr, "--- XXX input buffer full\n");
s->len = 0; // complete
}
/* locate the end of the header. If not found, just return */
body = strstr(s->inbuf, "\n\r\n") + 3;
if (body < s->inbuf) body = strstr(s->inbuf, "\n\n") + 2;
if (body < s->inbuf && s->len) return 0;
/* quick search for content length */
a = strcasestr(s->inbuf, "Content-length:");
if (a && a < body) {
sscanf(a + strlen("Content-length:") + 1, "%d", &clen);
if (me->verbose) fprintf(stderr, "content length = %d, body len %d\n",
clen, s->pos - (body - s->inbuf));
if (s->pos - (body - s->inbuf) != clen)
return 0;
}
/* no content length, hope body is complete */
/* XXX maybe do a multipass */
/* now parse the header */
for (row=0; (b = strsep(&c, "\r\n"));) {
if (*b == '\0') continue;
if (b > body) {
body = b;
break;
}
row++;
for (tok=0; (a = strsep(&b, " \t"));) {
if (*a == '\0') continue;
tok++;
if (row == 1) {
if (tok == 1) method = a;
if (tok == 2) resource = a;
}
}
}
s->reply = 1; /* body found, we move to reply mode. */
if (me->verbose) fprintf(stderr, "%s %s\n", method, resource);
if (me->verbose) fprintf(stderr, "+++ request body [%s]\n", body);
s->pos = 0;
if (!strcmp(method, "POST") && !strcmp(resource, "/u")) {
/* this is the ajax request */
return u_mode(me, s, body);
} else if (!strcmp(method, "GET") && !strncmp(resource, "/u?", 3)) {
/* same ajax request using GET */
return u_mode(me, s, resource+3);
} else { /* request for a file, map and serve it */
struct stat sb;
err = "invalid pathname";
if (!me->unsafe && resource[1] == '/')
goto error; /* avoid absolute pathnames */
for (a = resource+1; *a; a++) { /* avoid back pointers */
if (*a == '.' && a[1] == '.')
goto error;
}
if (!strcmp(resource, "/"))
resource = "/ajaxterm.html";
s->filep = open(resource+1, O_RDONLY);
err = "open failed";
if (s->filep < 0 || fstat(s->filep, &sb))
goto error;
err = "mmap failed";
/* linux wants MAP_PRIVATE or MAP_SHARED, not 0 */
s->map = mmap(NULL, (int)sb.st_size, PROT_READ, MAP_PRIVATE, s->filep, (off_t)0);
if (s->map == MAP_FAILED)
goto error;
s->body_len = sb.st_size;
s->len = sprintf(s->outbuf,
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\nContent-Length: %d\r\n\r\n",
getmime(resource+1), (int)sb.st_size);
return 0;
}
error:
if (s->filep >= 0)
close(s->filep);
s->len = sprintf(s->outbuf,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n\r\nResource %s : %s\n", resource, err);
return 0;
}
/*
* Handle I/O on the socket talking to the browser.
* We always use it in half duplex
*/
int sock_io(struct my_args *me, struct my_sock *s)
{
int l;
if (s->reply) {
/* first write the header, then set s->len negative and
* write the mapped file
*/
if (s->len > 0) {
l = write(s->socket, s->outbuf + s->pos, s->len - s->pos);
} else {
l = write(s->socket, s->map + s->pos, s->body_len - s->pos);
}
if (l <= 0)
goto write_done;
s->pos += l;
if (me->verbose) fprintf(stderr, "written1 %d/%d\n", s->pos, s->len);
if (s->pos == s->len) { /* header sent, move to the body */
s->len = -s->body_len;
s->pos = 0;
}
if (me->verbose) fprintf(stderr, "written2 %d/%d\n", s->pos, -s->len);
if (s->pos == -s->len) { /* body sent, close */
write_done:
if (me->verbose) fprintf(stderr, "reply complete\n");
/* the kindle wants shutdown before close */
shutdown(s->socket, SHUT_RDWR);
close(s->socket);
s->len = 0;
if (s->filep) {
if (s->map) munmap(s->map, s->body_len);
close(s->filep);
}
}
} else { /* accumulate request */
l = read(s->socket, s->inbuf + s->pos, s->len - s->pos);
if (me->verbose) fprintf(stderr, "read %p returns %d %s\n", s, l, s->inbuf);
if (l <= 0) {
fprintf(stderr, "buf [%s]\n", s->inbuf);
s->len = 0; // mark done with read
} else {
s->pos += l;
}
parse_msg(me, s); /* check if msg is complete */
}
return 0;
}
int shell_keyboard(struct my_args *me, struct my_sess *sh)
{
int l;
l = write(sh->master, sh->keys, sh->klen);
if (l <= 0)
return 1;
strcpy(sh->keys, sh->keys + l);
sh->klen -= l;
return 0;
}
/* process screen output from the shell */
int shell_screen(struct my_args *me, struct my_sess *p)
{
int l, spos;
char *s;
spos = strlen(p->sbuf);
l = read(p->master, p->sbuf + spos, sizeof(p->sbuf) - 1 - spos);
if (l <= 0) {
fprintf(stderr, "--- screen gives %d\n", l);
p->master = -1;
return 1;
}
spos += l;
p->sbuf[spos] = '\0';
s = page_append(p, p->sbuf); /* returns unprocessed pointer */
strcpy(p->sbuf, s);
return 0;
}
/*
* Main loop implementing web server and connection handling
*/
int mainloop(struct my_args *me)
{
fprintf(stderr, "listen on %s:%d\n",
inet_ntoa(me->sa.sin_addr), ntohs(me->sa.sin_port));
me->lfd = opensock(me->sa);
for (;;) {
int n, nmax;
struct my_sock *s, *nexts, **ps;
struct my_sess *p, *nextp, **pp;
fd_set r, w;
struct timeval to = { 5, 0 };
FD_ZERO(&r);
FD_ZERO(&w);
FD_SET(me->lfd, &r);
nmax = me->lfd;
for (s = me->socks; s; s = s->next) { /* handle sockets */
FD_SET(s->socket, s->reply ? &w : &r);
if (nmax < s->socket)
nmax = s->socket;
}
for (p = me->sess; p; p = p->next) { /* handle terminals */
FD_SET(p->master, &r);
if (nmax < p->master)
nmax = p->master;
if (p->klen) /* have bytes to send to keyboard */
FD_SET(p->master, &w);
}
n = select(nmax + 1, &r, &w, NULL, &to);
if (n == 0) {
fprintf(stderr, "select returns %d\n", n);
continue;
}
if (FD_ISSET(me->lfd, &r))
handle_listen(me);
for (ps = &me->socks, s = *ps; s; s = nexts) { /* scan sockets */
nexts = s->next;
if (FD_ISSET(s->socket, s->reply ? &w : &r))
sock_io(me, s);
if (s->len != 0) { /* socket still active */
ps = &s->next;
} else { /* socket dead, unlink */
*ps = s->next;
free(s);
}
}
for (pp = &me->sess, p = *pp; p ; p = nextp) { /* scan shells */
nextp = p->next;
if (FD_ISSET(p->master, &w))
shell_keyboard(me, p);
if (p->master >= 0 && FD_ISSET(p->master, &r))
shell_screen(me, p);
if (p->master >= 0) { /* session still active */
pp = &p->next;
} else { /* dead session, unlink */
*pp = p->next;
fprintf(stderr, "-- free session %p ---\n", p);
free(p);
}
}
}
return 0;
}
int main(int argc, char *argv[])
{
struct my_args me;
bzero(&me, sizeof(me));
me.sa.sin_family = PF_INET;
me.sa.sin_port = htons(8022);
inet_aton("127.0.0.1", &me.sa.sin_addr);
me.cmd = "login";
for ( ; argc > 1 ; argc--, argv++) {
if (!strcmp(argv[1], "--unsafe")) {
me.unsafe = 1;
continue;
}
if (!strcmp(argv[1], "--verbose")) {
me.verbose = 1;
continue;
}
if (argc < 3)
break;
if (!strcmp(argv[1], "--cmd")) {
me.cmd = argv[2];
argc--; argv++; continue;
}
if (!strcmp(argv[1], "--port")) {
me.sa.sin_port = htons(atoi(argv[2]));
argc--; argv++; continue;
}
if (!strcmp(argv[1], "--addr")) {
struct hostent *h = gethostbyname(argv[2]);
if (h) {
me.sa.sin_addr = *(struct in_addr *)(h->h_addr);
} else if (!inet_aton(argv[1], &me.sa.sin_addr)) {
perror("cannot parse address");
exit(1);
}
argc--; argv++; continue;
}
break;
}
mainloop(&me);
return 0;
}
myts.arm 000755 000423 000000 00000045121 11464701264 013024 0 ustar 00luigi wheel 000000 000000 ELF ( 4 3 4 ( p/ 4 4 4 4 4 4 / / 0 0 0 0 0 0 H H H Qtd /lib/ld-linux.so.3 GNU % 1 , . ( ' ) # " 0 &