myts.c000644 000423 000000 00000056017 11465553275 012502 0ustar00luigiwheel000000 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 7679 2010-11-07 14:02:04Z luigi $ Backend for web-based terminal The main instance of a program keeps a list of current sessions, and for each of them handles a child process which runs a shell and talks to the parent through a pseudo tty. In standalone mode, the process runs as a web server and so it can suspend requests for some time until they are handled. Data from the terminal are encoded as utf8 data from the shell are in a
 also utf8-encoded

 */

#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 */

#include "dynstring.h"
#include "dynstring.c"

#define KMAX	256	/* keyboard queue */
#define SMAX	256	/* keyboard queue */
#define	ROWS	25
#define	COLS	80
#define	INBUFSZ	4096	/* GET/POST queries */

/* 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 */
	dynstr response;

	/* 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;	/* cursor offset */
	int modified;	/* ... since last read */
	char *page;	/* 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
 * Returns a pointer to leftover chars.
 */
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;
}

/* process requests coming from the browser */
int u_mode(struct my_args *me, struct my_sock *ss, char *body)
{
    /* request parameters */
    char *s = NULL, *k = "";
    int refresh = 0, rows = 0, cols = 0, color=0;
    char *cur, *p, *p2;
    int i;

    struct my_sess *sh = NULL;	/* shell session for the request */

    /* prepare for errors */
    const char *ret = "
error in request
"; const char *src = NULL; /* extract parameters */ for (p = body; (cur = strsep(&p, "&")); ) { if (!*cur) continue; p2 = strsep(&cur, "="); if (!strcmp(p2, "s")) s = cur; // session id if (!strcmp(p2, "w")) cols = atoi(cur); // width if (!strcmp(p2, "h")) rows = atoi(cur); // height if (!strcmp(p2, "c")) color = atoi(cur); // color if (!strcmp(p2, "k")) k = cur; // keys if (!strcmp(p2, "r")) refresh = 1; /* force refresh */ } if (!s || !*s) { /* list sessions */ dsprintf(&ss->response, "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n\r\n" "

Active sessions

\n"); for (i = 0, sh = me->sess; sh; sh = sh->next, i++) { dsprintf(&ss->response, "%2d %s\n", i, sh->name, sh->name); } dsprintf(&ss->response, "

\n"); ss->len = ds_len(ss->response); return 0; } if (rows < 4 || rows > 80) rows = 25; if (cols < 10 || cols > 160) cols = 80; 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 + l2); if (!sh) goto error; sh->rows = rows; sh->cols = cols; sh->cur = 0; sh->name = (char *)(sh + 1); sh->page = sh->name + l2; memset(sh->page, ' ', pagelen); strcpy(sh->name, s); sh->next = me->sess; me->sess = sh; if (forkchild(sh, me->cmd)) { dsprintf(&ss->response, "HTTP/1.1 400 fork failed\r\n\r\n"); ss->len = ds_len(ss->response); return 0; } } unescape(k); /* silently drop chars in case of overflow */ strncat(sh->keys + sh->klen, k, sizeof(sh->keys) - 1 - sh->klen); sh->klen = strlen(sh->keys); rows = sh->rows; cols = sh->cols; sh->page[rows*cols] = '\0'; // ensure it is terminated XXX bug in cursor handling if (!refresh && !sh->modified) { /* no modifications, compact version */ ret = ""; /* unmodified page */ } else { src = sh->page; ret = "
";
	sh->modified = 0;
    }
error:
    dsprintf(&ss->response,
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/xml\r\n\r\n"
	    "%s", ret);
    if (src) {
	unsigned char done = '\0';

	/* convert text so that it does not interfere with the XML.
	 * mostly, we need utf8 encoding for 'special' characters
	 * Colors and the like are dealt with using ''
	 * (surely needed for the cursor).
	 * Make sure we always have rows*cols chars.
	 */
	for (i=0; i < rows*cols;) {
	    unsigned char cc = done ? done : (unsigned char)src[i];
	    if (!cc) done = cc = ' ';
	    if (sh && src == sh->page && i == sh->cur)
		dsprintf(&ss->response, "");
	    if (isalnum(cc) || index(" ", cc)) // maybe more, but who cares
		dsprintf(&ss->response, "%c", cc); /* unmodified chars */
	    else if (cc <= 0x7f)
		dsprintf(&ss->response, "%%%02x", cc);
	    else
		dsprintf(&ss->response, "%%%02x%%%02x",
			0xc0+(cc>>6), 0xc0+(cc&0x3f));
	    if (sh && src == sh->page && i == sh->cur)
		dsprintf(&ss->response, "");
	    if (++i % cols == 0)
		dsprintf(&ss->response, "\n"); /* unmodified chars */
	}
	dsprintf(&ss->response, "
"); } ss->len = ds_len(ss->response); if (me->verbose) fprintf(stderr, "response %s\n", ds_data(ss->response)); 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 = "", *resource = ""; 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")) { return u_mode(me, s, body); /* ajax request using POST */ } else if (!strcmp(method, "GET") && !strncmp(resource, "/u?", 3)) { return u_mode(me, s, resource+3); /* ajax request using GET */ } else { /* request for a file, map and serve it */ struct stat sb; char *query_args = resource; resource = strsep(&query_args, "?&"); 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; dsprintf(&s->response, "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %d\r\n\r\n", getmime(resource+1), (int)sb.st_size); s->len = ds_len(s->response); return 0; } error: if (s->filep >= 0) close(s->filep); dsprintf(&s->response, "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n\r\nResource %s : %s\n", resource, err); s->len = ds_len(s->response); 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, ds_data(s->response) + 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 = write(sh->master, sh->keys, sh->klen); if (l <= 0) return 1; /* error, currently ignored */ 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 *sh) { char *s; int spos = strlen(sh->sbuf); int l = read(sh->master, sh->sbuf + spos, sizeof(sh->sbuf) - 1 - spos); if (l <= 0) { fprintf(stderr, "--- shell read error, dead %d\n", l); sh->master = -1; /* report error. */ return 1; } spos += l; sh->sbuf[spos] = '\0'; sh->modified = 1; /* maybe not... */ s = page_append(sh, sh->sbuf); /* returns unprocessed pointer */ strcpy(sh->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; ds_free(s->response); 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++) { char *optval, *opt = argv[1]; /* options without arguments */ if (!strcmp(opt, "--unsafe")) { me.unsafe = 1; continue; } if (!strcmp(opt, "--verbose")) { me.verbose = 1; continue; } if (argc < 3) break; /* options with argument */ optval = argv[2]; argc--; argv++; if (!strcmp(opt, "--cmd")) { me.cmd = optval; continue; } if (!strcmp(opt, "--port")) { me.sa.sin_port = htons(atoi(optarg)); continue; } if (!strcmp(argv[1], "--addr")) { struct hostent *h = gethostbyname(optarg); 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); } continue; } break; } if (argc > 1) { fprintf(stderr, "invalid argument %s\n", argv[1]); exit(1); } mainloop(&me); return 0; } dynstring.c000644 000423 000000 00000016474 11465303715 013522 0ustar00luigiwheel000000 000000 /* * $Id: dynstring.c 3974 2009-11-10 17:31:17Z luigi $ * * An implementation of dynamic strings (and in general, extensible * data structures) inherited from the one i wrote myself for asterisk. * * Note that the internals of the dynstring are opaquE */ #include "dynstring.h" #include /* vsnprintf */ #include /* varargs */ #include #include /* bcopy */ #include #define START_SIZE 48 // initial size /* * This is the internal representation of the object -- a header * followed by an inline buffer. The entire chunk is malloc()ed or * realloc()ed as needed. * * 'len' represents the size of the buffer space, excluding the * space occupied by the structure. * Special case: len = 0 and used >0 represents a reference to * an external string (ds_readonly() will return true). * As an implementation detail, we make the last byte unavailable * for users so we put a NUL byte there and guarantee that strings * are well terminated. */ struct __dynstr { size_t len; /* The current size of the buffer */ size_t used; /* Amount of space used */ char str[0]; /* The string buffer */ }; static int ds_readonly(dynstr s) { return (s->len == 0 && s->used > 0); } const char *ds_data(dynstr s) { const char **pp; if (!s) return ""; if (s->len > 0) return s->str; if (s->used == 0) return ""; pp = (const char **)&(s->str); return pp[0]; } /* * Create a reference to an existing string. In this case the len * field is 0 and 'used' is the length of the string we point to. */ dynstr ds_ref(const char *p, int len) { dynstr d = ds_create(sizeof(const char *)); const char **pp = (const char **)&(d->str); pp[0] = p; d->len = 0; d->used = len; return d; } void *ds_free(dynstr s) { if (s) free(s); return NULL; } int ds_len(dynstr s) { return s ? s->used : 0; } /* * When returning the available space, decrement by 1 so we * can add a '\0' at the end without overwriting user data */ int ds_size(dynstr s) { return s ? s->len - 1 : 0; } void ds_reset(dynstr buf) { if (buf) { buf->used = 0; if (buf->len) buf->str[0] = '\0'; } } dynstr ds_create(int init_len) { dynstr buf; buf = (dynstr)calloc(1, sizeof(*buf) + init_len); if (buf == NULL) return NULL; buf->len = init_len; buf->used = 0; return buf; } static int dynstr_make_space(dynstr *buf, size_t new_len) { dynstr newbuf; if (buf == NULL) return 0; if (ds_readonly(*buf)) return -1; if (new_len <= (*buf)->len) return 0; /* success */ /* make it slightly larger than requested */ if (new_len < 1000) new_len += new_len; else new_len += 1000; newbuf = (dynstr)realloc(*buf, new_len + sizeof(struct __dynstr)); if (newbuf == NULL) return -1; *buf = newbuf; (*buf)->len = new_len; return 0; } static int __dynstr_helper(dynstr *buf, size_t max_len, int append, const char *fmt, va_list ap); #define DYNSTR_BUILD_RETRY -2 #define DYNSTR_BUILD_FAILED -3 /* * Append to a dynamic string using a va_list */ #define vadsprintf(buf, max_len, fmt, ap) \ ({ \ int __res; \ while ((__res = __dynstr_helper(buf, max_len, \ 1, fmt, ap)) == DYNSTR_BUILD_RETRY) { \ va_end(ap); \ va_start(ap, fmt); \ } \ (__res); \ }) /*! * Append to a dynamic string - same as sprintf(). */ int __attribute__ ((format (printf, 2, 3))) dsprintf(dynstr *buf, const char *fmt, ...) { int res; va_list ap; if (buf == NULL) return 0; va_start(ap, fmt); res = vadsprintf(buf, 0 /* max_len */, fmt, ap); va_end(ap); return res; } /* * Append a buffer to a dynamic string (and also a '\0' to ease printing). * If d == NULL only extend */ int ds_append(dynstr *buf, const void *d, int len) { int need; if (buf == NULL) return 0; if (*buf == NULL) *buf = ds_create(START_SIZE); if (*buf == NULL) return DYNSTR_BUILD_FAILED; if (len < 0) { /* the 'truncate' */ need = -len + 1; } else { need = (*buf)->used + len + 1; } if (need > (*buf)->len) { if (dynstr_make_space(buf, need)) return DYNSTR_BUILD_FAILED; } if (len < 0) { (*buf)->used = -len; } else { if (d) bcopy(d, (*buf)->str + (*buf)->used, len); (*buf)->used += len; } (*buf)->str[(*buf)->used] = '\0'; return 0; } int ds_truncate(dynstr *buf, int want) { if (buf && *buf && ds_readonly(*buf) && want <= (*buf)->used) { (*buf)->used = want; return 0; } return ds_append(buf, NULL, -want); } /* adjust used to the desired length */ int ds_adjust(dynstr *buf, int i, int recsize) { int l = ds_len(*buf); int want = (i+1)*recsize; if (l < want) ds_truncate(buf, want); return 0; } /* remove the initial n bytes from the string, shifting up the content */ int ds_shift(dynstr d, int n) { if (!d || n < 0 || n > d->used) return -1; d->used -= n; // residual size if (ds_readonly(d)) { /* for readonly string, shift instead of move */ const char **pp = (const char **)&(d->str); pp[0] += n; } else { bcopy(d->str + n, d->str, d->used); d->str[d->used] = '\0'; } return d->used; } __attribute__((format (printf, 4, 0))) static int __dynstr_helper(dynstr *buf, size_t max_len, int append, const char *fmt, va_list ap) { int res, need; int offset; if (buf == NULL) return 0; if (*buf == NULL) *buf = ds_create(START_SIZE); if (*buf == NULL) return DYNSTR_BUILD_FAILED; offset = (append && (*buf)->len) ? (*buf)->used : 0; if (max_len < 0) max_len = (*buf)->len; /* don't exceed the allocated space */ /* * Ask vsnprintf how much space we need. Remember that vsnprintf * does not count the final '\0' so we must add 1. */ res = vsnprintf((*buf)->str + offset, (*buf)->len - offset, fmt, ap); need = res + offset + 1; /* * If there is not enough space and we are below the max length, * reallocate the buffer and return a message telling to retry. */ if (need > (*buf)->len && (max_len == 0 || (*buf)->len < max_len) ) { if (max_len && max_len < need) /* truncate as needed */ need = max_len; else if (max_len == 0) /* if unbounded, give more room for next time */ need += 16 + need/4; if (dynstr_make_space(buf, need)) return DYNSTR_BUILD_FAILED; (*buf)->str[offset] = '\0'; /* Truncate the partial write. */ /* va_end() and va_start() must be done before calling * vsnprintf() again. */ return DYNSTR_BUILD_RETRY; } /* update space used, keep in mind the truncation */ (*buf)->used = (res + offset > (*buf)->len) ? (*buf)->len : res + offset; return res; } dynstring.h000644 000423 000000 00000004767 11465303720 013525 0ustar00luigiwheel000000 000000 /* * $Id: dynstring.h 3771 2009-10-22 19:01:59Z luigi $ * * An implementation of dynamic strings (and in general, extensible * data structures) inherited from the one i wrote myself for asterisk. * * This is similar to the libsbuf that is available in FreeBSD * * USE: declare the dynamic string: dynstr s = NULL; * then use as asprintf(), e.g. dsprintf(&s, fmt, ...); * or, to append a chunk of bytes: ds_append(&s, ptr, len) * * Use ds_len(s), ds_data(s), ds_reset(s), ds_free(s) to get the * length, data pointer, reset the content, and free the memory. * * This code has been originally designed for strings, however * ds_append() supports appending arbitrary chunks of bytes to * the structure, and in fact it is very convenient to implement * some form of dynamic arrays. */ #ifndef __DYNSTRING_H #define __DYNSTRING_H typedef struct __dynstr * dynstr; /* sprintf and append bytes to a dynamic string */ int dsprintf(dynstr *s, const char *fmt, ...); /* append a chunk of bytes to the structure */ int ds_append(dynstr *s, const void *d, int len); /* truncate or extend to the desired size */ int ds_truncate(dynstr *s, int desired_size); /* Adjust the array so that it includes an entry of index i * and size recsize (i.e. at least recsize*[i+1] bytes) */ int ds_adjust(dynstr *s, int i, int recsize); /* Return a pointer to the content (or to "" if empty). * The function never returns NULL; use ds_len() to tell if the * block of memory is not allocated or otherwise empty. */ const char *ds_data(dynstr s); /* return the length in bytes of the content */ int ds_len(dynstr s); // returns the string lenght /* return the total size of the allocated buffer */ int ds_size(dynstr s); // returns the buffer size /* remove the initial n bytes from the string, shifting content up */ int ds_shift(dynstr s, int n); // returns the string lenght /* reset the buffer to the empty string, without deallocating */ void ds_reset(dynstr s); // resets the buffer to empty string /* Create a dynstr with given initial size. * Note that the 'used' field is set to 0 so ds_len will return 0 * Normally you don't need to call ds_create unless you want * to set special properties on the string such as bounded size. */ dynstr ds_create(int len); /* * Create a dynamic string that references an external buffer. * The string is readonly. */ dynstr ds_ref(const char *base, int len); /* frees the space used. Returns NULL for convenience */ void *ds_free(dynstr s); // frees the space #endif /* __DYNSTRING_H */ ajaxterm.css000644 000423 000000 00000003242 11465125541 013646 0ustar00luigiwheel000000 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.html000644 000423 000000 00000001333 11465135701 014020 0ustar00luigiwheel000000 000000 Ajaxterm - Luigi Rizzo version
ajaxterm.js000644 000423 000000 00000017037 11465136346 013506 0ustar00luigiwheel000000 000000 /* * $Id: ajaxterm.js 7677 2010-11-06 00:33:44Z 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 = function(id, width, height, keylen, sid) { 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; if (!sid) { /* generate a session ID if not supplied */ sid = ''; var alphabet = 'abcdefghijklmnopqrstuvwxyz'; var l = alphabet.length; for (var i=0; i < keylen; i++) sid += alphabet.charAt(Math.round(Math.random()*l)); } /* query0 is the base URL for a query */ var query0="s="+sid+"&w="+width+"&h="+height; var query1=query0+"&c=1&k="; /* current query */ var timeout; /* callback for the update request */ var error_timeout; /* the error callback */ var keybuf=''; /* keys to be transmitted */ var sending=0; /* set when we have a pending request */ var rmax=1; /* delay between refresh requests */ /* elements in the top bar */ var div=document.getElementById(id); var dstat=document.createElement('pre'); /* status line */ var opt_get=document.createElement('a'); var opt_color=document.createElement('a'); var sdebug=document.createElement('span'); var dterm=document.createElement('div'); function debug(s) { setHTML(sdebug, s); } function error() { debug("Connection lost at "+((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 update() { if (sending) return; sending=1; var r=new XMLHttpRequest(); var send=keybuf; keybuf = ''; 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" || de.tagName=="p") { 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 += 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( encodeURIComponent(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() { 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'; 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); } else { opt_get.attachEvent("onclick", do_get); opt_color.attachEvent("onclick", do_color); } document.onkeypress=keypress; document.onkeydown=keydown; document.onkeyup=keyup; timeout=window.setTimeout(update,100); } init(); debug('Session: ' + sid); } README000644 000423 000000 00000004244 11465553577 012222 0ustar00luigiwheel000000 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) + dynstring.[ch] (my library for dynamic strings) + 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 myts.arm000755 000423 000000 00000065564 11465553526 013050 0ustar00luigiwheel000000 000000 ELF((4Q4 (pM444444MMMMMMMMHHH Qtd/lib/ld-linux.so.3GNU%836-,.'&1* 50+ 4!7$) "/   (2#%odĊHЊC܊Tt  $0a<HHTy`ldxdXd|dNo^X̋`؋0xU Od 1,8DPP\tihdWt< Pt0&O-OBȌԌPg4`8(4\libutil.so.1__gmon_start___Jv_RegisterClassesforkptylibc.so.6strcasestrsocketstrcpyexithtonsstrncmpinet_atonperrorinet_ntoalistenselectreallocabortmmapcallocstrlenmemsetstrstrstrcspnbindreadshutdownvsnprintfmemcpysetsockoptraisebzero__ctype_b_locsscanfoptargstderrmunmapgethostbynameindexexecvpstrncatfwritebcopyatoicloseopenfprintfacceptfcntlstrsepbcmpstrcmp__libc_start_mainntohsfree__fxstatGLIBC_2.4 ii 9ii OO,O/NNNNNNNN N N O O O OOOOO O$O(O,O0O4O8O<000 0 0K0(0 0,0 000 00?S00 0K000 00S7 00 000 00S 000S000K0[S<0@0 *0<0 ( K,K00K0 0b0[AS 80$"80 2R80$"80 2 c80$"( R000B(0 Q0[BS80$80 "$00bQ80$"80 2 80$"( R000B(0 Q0[CS80 " 0 c(0R80 " 00c0C(0 80$"(0 80$"0[DS (  0R 0(0 80$"(0 c80$"0[HS 0[fS80"(0R802(0 80 ",0R80 2,0 (0 C80 2,00 C80$"0[JS/ 0R (0S80 "$00b(0 80,"80$2 (0  f(0S80,"80$2  Z(0S.80,2$  8 0$2M0[KS 0R (0S80 " 00c(0 80,"80$2 (0  r3(0S 0[?S0[lS 0[?S41(,@0 [00 00c00 0  080,"80$2 <00080 2 C 0R 80$2 80$"80$"$0R|00 [t<00<0 <00S<00S 80 <000 p<0@0 @0KxOH- M  0Ka028#80K0 28#80K0 0 K 01 0 00Sp00l 60 0 00S 00 00 0 KN0 0 0KOH-lMX \ ` 0H0 8D0 0@0 0<0 080 040 0(0 7$0 0 0 `0P0 BL00S> L0K000 00SL0H0 00SL0080 0l0SL00<0 0D0SL0040 00SL0D0 00S0@0 P0Kc0L0 L0SH0S H00S2\0:$00,0 X0 0(0 \0*$ (0(000\,  0(00(0 ,00,0 (0S\0:$0\ 609 \0 h k<0S<0PS0<0 80 S80SP080 0,0 X0 0(0 (00H0S (00(0 ,00,0 (0S(0SW< 8000 H 000 < 800  00#>0(0 (0Ss ( <02( 80 2( 0$2(0#.(0 (0 0 (0,"(0,2  (00HX0 (0 X (0 0X00(0S \0:$0Y\  40 \0 0h0 D(0 (00 (000cD (000 (0 (02<0 (0 280 (0,< 80 00@0S(0(2S@3$0 (0,2 0 ,3$0 ( 0(2\0:$0 $  0S 00K0,0 ~0[S,0 00d0  [d d 0 0K 0[S 0 0K 0[0K(0S (0," 0R (0$",0R\0:$0L0 0[0000S 0[0S \0:$0 [ 0[ 10 K0S410 h0 10000 0P0 h1 \10s h H10J$@00e0h 0  10S 0 00 0 0S0 00:$0 0 006 0 00 0K,O<X\`pt( 0,$TH-M  00S 00S0@  30 0 00 0 00c 00 0 2 0 0 2 0 00c 00 0S80 0 0 0,0S d2 0 00L 0 00R 20 c0  0 00,0S 1 0 000c 0 000cRd0,0S 10 0000 00 \10SI 00!0S  1 100!0500$ 0 00 0 00c k00 0,0S 0 0$00 00S h0 0$0l  000 0 0 {0K$0,O(H-M  0 0 00 )00 0S00 00 0060 0 c0 00 0KH- M  0G?0 0 0 0G/ 0 00c 00 0S ʨ00 , 0 00 0  00 0 0F/0 00 0(20G?00 0G?0 0 0KO0H- VMH 5PH1@H100 H1 H1 5D K0K0 00  01000 0SQ?K0 00  01000 0SH102 1 K00H10 02 1 K00H1080 H1040 .400S QK\  K\! 4002X1 X1T1 400S QKP KP X1P!400 020T\140 80R40080 40040 40SH1 0(0 :(0 02 1 K00(0 0 02 1 K00(0 80R(0 080 (00S (0 02,#1 K00(0 0 02"1 K00(00(0 (0S80 KQKD0K0 0"0<0 <0S20< #6H102 1 K00 H100R300S HeH10,0 ,0040 /40000 400S Q/KL! 0KL1 4002L!4000R300S H4400S 40,0 40 ,0 4 t1040040 40SH1 0 0 00(0 C(00$0 (0 02 !1 K00 (0 00R300S H(<(0 0S(0 02 1 K00 (0 00R300S H(P(0 0S(0 0 (0 0 000<( (D$0(0 (0SO$$8H-HMH L @0K0|04K403K@0K0 2200 jL000 0 0S00 W y0S00 NH0STL0000 H00CH0 L00L0 c0S000 8 hZ0S X1008#803K&L000E0S&10500 0S 0000<0 L00 @0K00S'H00CH0 L00L0 H0SH0S h0 L000T @0K0KVP\dp|OOQ  aB Qp 00`BSoq /oo @ r S?C S?C S>C S>C S=C  S=C Serror in request
=swhckr&HTTP/1.1 200 OK Content-Type: text/html

Active sessions

%2d %s

HTTP/1.1 400 fork failed
HTTP/1.1 200 OK
Content-Type: text/xml

%s %c%%%02x%%%02x%%%02x
response %s cannot create socket cannot reuseaddrbindlistenlistening socket listen failed alloc failed text/plaingeneric error--- XXX input buffer full Content-length:%dcontent length = %d, body len %d %s %s +++ request body [%s] POST/uGET/u??&invalid pathname//ajaxterm.htmlopen failedmmap failedHTTP/1.1 200 OK Content-Type: %s Content-Length: %d HTTP/1.1 200 OK Content-Type: text/plain Resource %s : %s written1 %d/%d written2 %d/%d reply complete read %p returns %d %s buf [%s] --- shell read error, dead %d listen on %s:%d select returns %d -- free session %p --- 127.0.0.1login--unsafe--verbose--cmd--port--addrcannot parse addressinvalid argument %s d9 MMhd  Nooo0M$8GCC: (Sourcery G++ Lite 2008q3-72) 4.3.2 | $  | t xlD  | dA.aeabi$ARM10TDMI .symtab.strtab.shstrtab.interp.note.ABI-tag.hash.dynsym.dynstr.gnu.version.gnu.version_r.rel.dyn.rel.plt.init.text.fini.rodata.ARM.exidx.eh_frame.init_array.fini_array.jcr.dynamic.got.data.bss.comment.debug_frame.ARM.attributes44#HH 1hh|7 ?ddGo00pTo@c l  u p {(( :GGpM MMMMMMMMMNNOOOOO*OpxP/PXV p c|4Hhd0   (  MMMMNOO` ` |   H( OT   )M6   LO[MM  ȍ MMЍX Ѝ    đ ȑ ,@ OO  l p     D  ԭ  T l L X   x  p x p   $ , d t   d .M:NPMaMtM   }O dĊHЊt X ܊( T l  $t9 H \ q  L $0d" <HT`p x '6ldFxdVdgOt |dO$ d" d X̋`؋0'3` =O$  XxhOu ȑ Oxl O 8t l8 , 8D/P 8PPJ\t\hdmt< PO0Op ( D  P( O 4   0A" G SOew OȌԌPO`8(2$ ?0 H4ZjTH q v call_gmon_start$a$dinit.ccrtstuff.c__JCR_LIST____do_global_dtors_auxcompleted.5877__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entrymyts.cds_readonlydynstr_make_space__dynstr_helpermime_typesC.130.5367.divsi3_nodiv0shiftelf-init.cfstat.c__FRAME_END____JCR_END___GLOBAL_OFFSET_TABLE___init_array_end__init_array_start_DYNAMICdata_startopen@@GLIBC_2.4mmap@@GLIBC_2.4abort@@GLIBC_2.4__libc_csu_finiparse_msgstrcasestr@@GLIBC_2.4_start__libc_start_main@@GLIBC_2.4getmimeshell_keyboardinet_ntoa@@GLIBC_2.4__gmon_start___Jv_RegisterClassesvsnprintf@@GLIBC_2.4strsep@@GLIBC_2.4_fini__fstatds_sizelisten@@GLIBC_2.4calloc@@GLIBC_2.4__aeabi_idiv0htons@@GLIBC_2.4memset@@GLIBC_2.4execvp@@GLIBC_2.4perror@@GLIBC_2.4page_appendmainloop_IO_stdin_usedfree@@GLIBC_2.4read@@GLIBC_2.4write@@GLIBC_2.4__data_startsock_ioaccept@@GLIBC_2.4__bss_start__socket@@GLIBC_2.4ds_shiftinet_aton@@GLIBC_2.4__aeabi_ldiv0ds_refntohs@@GLIBC_2.4bcopy@@GLIBC_2.4strlen@@GLIBC_2.4sscanf@@GLIBC_2.4__exidx_endds_appendmemcpy@@GLIBC_2.4__divsi3bcmp@@GLIBC_2.4__dso_handlestrcpy@@GLIBC_2.4dsprintf__end__strncat@@GLIBC_2.4__libc_csu_init__bss_end__raise@@GLIBC_2.4atoi@@GLIBC_2.4ds_createmyerrforkpty@@GLIBC_2.4shutdown@@GLIBC_2.4bind@@GLIBC_2.4ds_resetstrstr@@GLIBC_2.4select@@GLIBC_2.4close@@GLIBC_2.4fwrite@@GLIBC_2.4forkchild__ctype_b_loc@@GLIBC_2.4__bss_startfprintf@@GLIBC_2.4_bss_end__ds_adjustds_data__aeabi_idivmodshell_screen_endds_freeunescapehandle_listenfcntl@@GLIBC_2.4bzero@@GLIBC_2.4fstatds_truncatestderr@@GLIBC_2.4munmap@@GLIBC_2.4__fxstat@@GLIBC_2.4u_modeoptarg@@GLIBC_2.4index@@GLIBC_2.4strcspn@@GLIBC_2.4_edatastrncmp@@GLIBC_2.4gethostbyname@@GLIBC_2.4realloc@@GLIBC_2.4__exidx_startsetsockopt@@GLIBC_2.4__aeabi_idivopensockstrcmp@@GLIBC_2.4exit@@GLIBC_2.4ds_lenmain_init