add websocket support, remote terminal as example

This commit is contained in:
lxsang 2016-11-12 17:39:11 +01:00
parent 725deacf9f
commit eb63cd0fa4
17 changed files with 6625 additions and 94 deletions

View File

@ -1,6 +1,13 @@
CC=gcc
EXT=dylib
SERVER=plugin_manager.o plugins/ini.o http_server.o plugins/dictionary.o plugins/sha1.o plugins/utils.o
SERVER=plugin_manager.o \
plugins/ini.o \
http_server.o \
plugins/dictionary.o \
plugins/base64.o \
plugins/sha1.o \
plugins/ws.o \
plugins/utils.o
SERVERLIB=-lpthread -ldl
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
@ -15,7 +22,15 @@ CFLAGS=-W -Wall -g -std=c99 -D DEBUG -D USE_DB $(PF_FLAG)
#-lsocket
PLUGINS= dummy.$(EXT) fileman.$(EXT) pluginsman.$(EXT) wterm.$(EXT) nodedaemon.$(EXT) cookiex.$(EXT)
PLUGINSDEP = plugins/ini.o plugins/plugin.o plugins/dbhelper.o plugins/dictionary.o plugins/utils.o plugins/list.o plugins/sha1.o
PLUGINSDEP = plugins/ini.o \
plugins/plugin.o \
plugins/dbhelper.o \
plugins/dictionary.o \
plugins/base64.o \
plugins/utils.o \
plugins/ws.o \
plugins/sha1.o \
plugins/list.o
PLUGINLIBS = -lsqlite3
main: httpd plugins
@ -40,7 +55,7 @@ plugins: $(PLUGINS)
clean: sclean pclean
sclean:
rm -f *.o build/httpd
rm -f *.o $(BUILDIRD)/httpd
pclean:
rm -rf $(BUILDIRD)/plugins/* plugins/*.o
-for file in plugins/* ;do \

BIN
build/.DS_Store vendored

Binary file not shown.

BIN
build/htdocs/.DS_Store vendored

Binary file not shown.

View File

@ -194,7 +194,36 @@ var Terminal = (function () {
return TerminalConstructor
}());
var wtermobj;
var nest_callback;
var socket = new WebSocket("ws://127.0.0.1:9191/wterm?q=test");
//ws.binaryType = 'arraybuffer';
socket.onopen = function(){}
//{
// Web Socket is connected, send data using send()
// var msg = "Lorem Ipsum ";
// ws.send(msg);
//ws.send(array);
// alert("Message is sent...");
//};
socket.onmessage = function (e) {
if(wtermobj)
{
wtermobj.print(e.data);
var par = $("#wterm");
par.animate({
scrollTop: par.get(0).scrollHeight
}, 0);
wtermobj.input("antd> ",nest_callback);
}
};
socket.onclose = function()
{
// websocket is closed.
alert("Connection is closed...");
};
var wterm_config = {
name: 'wterm_layout',
panels: [
@ -210,25 +239,13 @@ var wterm_config = {
{
wtermobj = new Terminal();
wtermobj.setTextSize(11);
var nest_callback = function(data)
nest_callback = function(data)
{
if(data.length>0)
{
//send to server
var webtty = new EventSource('/wterm?cmd='+data);
webtty.onmessage = function (e) {
wtermobj.print(e.data);
var par = $("#wterm");
par.animate({
scrollTop: par.get(0).scrollHeight
}, 0);
};
webtty.onerror = function(e)
{
// finish the command
webtty.close();
wtermobj.input("antd> ",nest_callback);
}
//alert("sent " + data);
socket.send(data);
//alert("sent "+data);
}
else
{

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<title>term.js</title>
<style>
html {
background: #555;
}
h1 {
margin-bottom: 20px;
font: 20px/1.5 sans-serif;
}
.terminal {
float: left;
border: #000 solid 5px;
font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 11px;
color: #f0f0f0;
background: #000;
}
.terminal-cursor {
color: #000;
background: #f0f0f0;
}
</style>
<script src="term/term.js"></script>
<body>
<h1>Terminal</h1>
</body>
<script>
window.onload = function() {
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};
var socket = new WebSocket("ws://127.0.0.1:9191/wterm?q=test");
var term = null;
socket.onopen = function(){
term = new Terminal({
cols: 80,
rows: 24,
useStyle: true,
screenKeys: true,
cursorBlink: false
});
term.on('data', function(data) {
socket.send(data);
});
term.on('title', function(title) {
document.title = title;
});
term.open(document.body);
term.write('\x1b[31mWelcome to term.js!\x1b[m\r\n');
};
socket.onmessage = function (e) {
if(term && e.data)
{
term.write(e.data.replaceAll("\n","\r\n"));
}
};
socket.onclose = function()
{
// websocket is closed.
alert("Connection is closed...");
};
window.onbeforeunload = function(e) {
if(socket) socket.close();
};
};
</script>
</html>

5977
build/htdocs/term/term.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -42,14 +42,45 @@ dictionary decode_request(int client,const char* method,const char* query)
char * token;
if(strcmp(method,"GET") == 0)
{
// this for check if web socket is enabled
int ws= 0;
char* ws_key = NULL;
while((line = read_line(client)) && strcmp("\r\n",line))
{
token = strsep(&line,":");
trim(token,' ');
if(token != NULL &&strcasecmp(token,"Cookie") == 0)
{
if(!cookie) cookie = decode_cookie(line);
}
else if(token != NULL && strcasecmp(token,"Upgrade") == 0)
{
// verify that the connection is upgrade to websocket
trim(line, ' ');
trim(line, '\n');
trim(line, '\r');
if(line != NULL && strcasecmp(line,"websocket") == 0)
ws = 1;
} else if(token != NULL && strcasecmp(token,"Sec-WebSocket-Key") == 0)
{
// get the key from the client
trim(line, ' ');
trim(line, '\n');
trim(line, '\r');
ws_key = strdup(line);
}
}
request = decode_url_request(query);
if(ws && ws_key != NULL)
{
ws_confirm_request(client, ws_key);
free(ws_key);
// insert wsocket flag to request
// plugin should handle this ugrade connection
// not the server
if(!request) request = dict();
dput(request,"__web_socket__","1");
}
}
else
{
@ -122,6 +153,40 @@ void __px(const char* data,int size)
printf("\n");
}
/**
* Send header to the client to confirm
* that the websocket is accepted by
* our server
*/
void ws_confirm_request(int client, const char* key)
{
char buf[256];
char rkey[128];
char sha_d[20];
char base64[64];
strcpy(rkey,key);
strcat(rkey,WS_MAGIC_STRING);
//printf("RESPONDKEY '%s'\n", rkey);
SHA1_CTX context;
SHA1_Init(&context);
SHA1_Update(&context, rkey, strlen(rkey));
SHA1_Final(&context, sha_d);
Base64encode(base64, sha_d, 20);
//printf("Base 64 '%s'\n", base64);
// send accept to client
sprintf(buf, "HTTP/1.1 101 Switching Protocols\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Upgrade: websocket\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Connection: Upgrade\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Sec-WebSocket-Accept: %s\r\n",base64);
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
LOG("%s\n", "Websocket is now enabled for plugin");
}
/**
* Decode the cookie header to a dictionary
* @param client The client socket

View File

@ -7,7 +7,7 @@
#define FORM_MULTI_PART "multipart/form-data"
#define APP_JSON "application/json"
#define PLUGIN_HANDLER "handler"
#define WS_MAGIC_STRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
struct plugin_entry {
struct plugin_entry *next;
char *pname;
@ -22,6 +22,7 @@ void unload_all_plugin();
void unload_plugin(struct plugin_entry*);
void unload_plugin_by_name(const char*);
void * plugin_from_file(char* name);
void ws_confirm_request(int, const char*);
char* post_url_decode(int client,int len);
dictionary decode_url_request(const char* query);
dictionary decode_request(int client,const char* method,const char* query);

BIN
plugins/.DS_Store vendored

Binary file not shown.

View File

@ -270,3 +270,7 @@ void unknow(int client)
html(client);
__t(client,"404 API not found");
}
int ws_enable(dictionary dic)
{
return (dic != NULL && R_INT(dic,"__web_socket__") == 1);
}

View File

@ -7,6 +7,7 @@
#include "dictionary.h"
#include "list.h"
#include "ini.h"
#include "ws.h"
#define SERVER_NAME "antd"
#define IS_POST(method) (strcmp(method,"POST")== 0)
@ -62,3 +63,4 @@ void clear_cookie(int, dictionary);
/*Default function for plugin*/
void handler(int, const char*,const char*,dictionary);
void unknow(int);
int ws_enable(dictionary);

44
plugins/rterm/rterm.c Normal file
View File

@ -0,0 +1,44 @@
#include "../plugin.h"
#define TEXT "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
void pexit()
{
}
void handler(int cl, const char* m, const char* rqp, dictionary rq)
{
//html(cl);
ws_msg_header_t* h = NULL;
if(ws_enable(rq))
{
printf("Doc: %s\n","Websocket is available" );
while(1)
{
h = ws_read_header(cl);
if(h)
{
if(h->opcode == WS_CLOSE)
{
printf("WARNING: Connection close\n");
break;
}
else if(h->opcode == WS_TEXT)
{
char buff[1025];
int l;
while((l = ws_read_data(cl,h,1024,buff)) != -1)
{
buff[l] = '\0';
printf("Received:%d '%s'\n", l, buff);
}
ws_t(cl, TEXT);
ws_t(cl, "test");
}
free(h);
}
}
}
printf("Child process exit\n");
}

View File

@ -261,5 +261,5 @@ void digest_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE], char *output)
//sprintf(c, " ");
//c += 1;
}
//*(c - 1) = '\0';
*c = '\0';
}

View File

@ -37,6 +37,7 @@ THE SOFTWARE.
#include <time.h>
#include <stdint.h>
#include "sha1.h"
#include "base64.h"
#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))
#define EQU(a,b) (strcmp(a,b) == 0)

199
plugins/ws.c Normal file
View File

@ -0,0 +1,199 @@
#include "ws.h"
/**
* Read a frame header
* based on this header, we'll decide
* the appropriate handle for frame data
*/
ws_msg_header_t * ws_read_header(int client)
{
uint8_t byte;
uint8_t bytes[8];
ws_msg_header_t* header = (ws_msg_header_t*) malloc(sizeof(*header));
// get first byte
if(recv(client, &byte, sizeof(byte), 0) <0) goto fail;
if(BITV(byte,6) || BITV(byte,5) || BITV(byte,4)) goto fail;// all RSV bit must be 0
//printf("FIN: %d, RSV1: %d, RSV2: %d, RSV3:%d, opcode:%d\n", BITV(byte,7), BITV(byte,6), BITV(byte,5), BITV(byte,4),(byte & 0x0F) );
// find and opcode
header->fin = BITV(byte,7);
header->opcode = (byte & 0x0F);
// get next byte
if(recv(client, &byte, sizeof(byte), 0) <0) goto fail;
//printf("MASK: %d paylen:%d\n", BITV(byte,7), (byte & 0x7F));
// check mask bit, should be 1
if(!BITV(byte,7))
{
// close the connection with protocol error
ws_close(client, 1002);
goto fail;
}
// get the data length of the frame
int len = (byte & 0x7F);
if(len <= 125)
{
header->plen = len;
} else if(len == 126)
{
if(recv(client,bytes, 2*sizeof(uint8_t), 0) <0) goto fail;
header->plen = (bytes[0]<<8) + bytes[1];
} else
{
//read only last 4 byte
if(recv(client,bytes, 8*sizeof(uint8_t), 0) <0) goto fail;
header->plen = (bytes[4]<<24) + (bytes[5]<<16) + (bytes[6] << 8) + bytes[7] ;
}
//printf("len: %d\n", header->plen);
// last step is to get the maskey
if(recv(client,header->mask_key, 4*sizeof(uint8_t), 0) <0) goto fail;
//printf("key 0: %d key 1: %d key2:%d, key3: %d\n",header->mask_key[0],header->mask_key[1],header->mask_key[2], header->mask_key[3] );
// check wheather it is a ping or a close message
// process it and return NULL
//otherwise return the header
//return the header
switch(header->opcode){
case WS_CLOSE: // client requests to close the connection
// send back a close message
ws_close(client,1000);
//goto fail;
break;
case WS_PING: // client send a ping
// send back a pong message
pong(client,header->plen);
break;
default: break;
}
return header;
fail:
free(header);
return NULL;
}
/**
* Read data from client
* and unmask data using the key
*/
int ws_read_data(int client, ws_msg_header_t* header, int len, uint8_t* data)
{
// if len == -1 ==> read all remaining data to 'data';
if(header->plen == 0) return 0;
int dlen = (len==-1 || len > header->plen)?header->plen:len;
if((dlen = recv(client,data, dlen, 0)) <0) return -1;
header->plen = header->plen - dlen;
// unmask received data
for(int i = 0; i < dlen; ++i)
data[i] = data[i]^ header->mask_key[i%4];
return dlen;
}
void _send_header(int client, ws_msg_header_t header)
{
uint8_t byte = 0;
uint8_t bytes[8];
for(int i=0; i< 8; i++) bytes[i] = 0;
//first byte |FIN|000|opcode|
byte = (header.fin << 7) + header.opcode;
send(client, &byte, 1, 0);
// second byte, payload length
// mask = 0
if(header.plen <= 125)
{
byte = header.plen;
send(client, &byte, 1, 0);
}
else if(header.plen < 65536) // 16 bits
{
byte = 126;
bytes[0] = (header.plen) >> 8;
bytes[1] = (header.plen) & 0x00FF;
send(client, &byte, 1, 0);
send(client, &bytes, 2, 0);
}
else // > 16 bits
{
byte = 127;
bytes[4] = (header.plen) >> 24;
bytes[5] = ((header.plen)>>16) & 0x00FF;
bytes[6] = ((header.plen)>>8) & 0x00FF;
bytes[7] = (header.plen) & 0x00FF;
send(client, &byte, 1, 0);
send(client, &bytes, 8, 0);
}
}
/**
* send a text data frame to client
*/
void ws_t(int client, const char* data)
{
ws_msg_header_t header;
header.fin = 1;
header.opcode = WS_TEXT;
header.plen = strlen(data);
_send_header(client,header);
send(client, data, header.plen,0);
}
/**
* send a binary data fram to client
* not tested yet, but should work
*/
void ws_b(int client, uint8_t* data, int l)
{
ws_msg_header_t header;
header.fin = 1;
header.opcode = WS_BIN;
header.plen = l;
_send_header(client,header);
send(client, data, header.plen,0);
}
/**
* Not tested yet
* but should work
*/
void pong(int client, int len)
{
ws_msg_header_t pheader;
pheader.fin = 1;
pheader.opcode = WS_PONG;
pheader.plen = len;
uint8_t data[len];
if(recv(client,data, len, 0) < 0) return;
_send_header(client, pheader);
send(client, data, len, 0);
}
/*
* Not tested yet, but should work
*/
void ws_close(int client, unsigned int status)
{
ws_msg_header_t header;
header.fin = 1;
header.opcode = WS_CLOSE;
header.plen = 2;
uint8_t bytes[2];
bytes[0] = status >> 8;
bytes[1] = status & 0xFF;
_send_header(client, header);
send(client,bytes,2,0);
}
int ws_status(int client)
{
fd_set sk;
FD_ZERO(&sk);
FD_SET(0, &sk);
FD_SET(client, &sk);
int result = select(client + 1, sk, NULL, NULL, NULL);
if(result == 1)
{
return FD_ISSET(client, &sk);
}
return result;
}

28
plugins/ws.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef WS_H
#define WS_H
#include <sys/socket.h>
#include <stdio.h>
#include <stdint.h>
#define BITV(v,i) ((v & (1 << i)) >> i)
#define WS_TEXT 0x1
#define WS_BIN 0x2
#define WS_CLOSE 0x8
#define WS_PING 0x9
#define WS_PONG 0xA
typedef struct{
uint8_t fin;
uint8_t opcode;
unsigned int plen;
uint8_t mask_key[4];
} ws_msg_header_t;
ws_msg_header_t * ws_read_header(int);
void ws_t(int , const char* );
void ws_b(int , uint8_t* data, int);
void ws_close(int, unsigned int);
void pong(int client, int len);
int ws_read_data(int , ws_msg_header_t*, int, uint8_t*);
int ws_status(int);
#endif

View File

@ -1,88 +1,191 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <string.h>
#include "../plugin.h"
void init();
call __init__ = init;
void init()
{
}
void pexit()
{
}
void handler(int cl, const char* m, const char* rqp, dictionary rq)
{
ws_msg_header_t* h = NULL;
if(ws_enable(rq))
{
int read_buf(int fd, char*buf,int size)
int fdm, fds;
int rc;
char buff[1024];
// Check arguments
fdm = posix_openpt(O_RDWR);
if (fdm < 0)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n'))
{
n = read(fd, &c, 1);
if (n > 0)
{
buf[i] = c;
i++;
LOG("Error %d on posix_openpt()\n", errno);
ws_close(cl, 1011);
return ;
}
rc = grantpt(fdm);
if (rc != 0)
{
LOG("Error %d on grantpt()\n", errno);
ws_close(cl, 1011);
return ;
}
rc = unlockpt(fdm);
if (rc != 0)
{
LOG( "Error %d on unlockpt()\n", errno);
ws_close(cl, 1011);
return ;
}
// Open the slave side ot the PTY
fds = open(ptsname(fdm), O_RDWR);
// Create the child process
if (fork())
{
fd_set fd_in;
// FATHER
// Close the slave side of the PTY
close(fds);
int max_fdm;
while (1)
{
FD_ZERO(&fd_in);
//FD_SET(0, &fd_in);
FD_SET(fdm, &fd_in);
FD_SET(cl,&fd_in);
max_fdm = fdm>cl?fdm:cl;
rc = select(max_fdm + 1, &fd_in, NULL, NULL, NULL);
switch(rc)
{
case -1 :
LOG("Error %d on select()\n", errno);
ws_close(cl, 1011);
return;
default :
{
// If data is on websocket side
if (FD_ISSET(cl, &fd_in))
{
h = ws_read_header(cl);
if(h)
{
if(h->opcode == WS_CLOSE)
{
LOG("%s\n","Websocket: connection closed");
write(fdm, "exit\n", 5);
return;
}
else if(h->opcode == WS_TEXT)
{
int l;
while((l = ws_read_data(cl,h,sizeof(buff),buff)) > 0)
{
//buff[l] = '\0';
write(fdm, buff, l);
//ws_t(cl,buff);
}
/*if(l == -1)
{
printf("EXIT FROM CLIENT \n");
write(fdm, "exit\n", 5);
return;
}*/
}
free(h);
}
else if(n == -1) return n;
else
c = '\n';
}
buf[i] = '\0';
return i;
}
void handler(int client, const char* m, const char* rqp, dictionary rq)
{
textstream(client);
int filedes[2];
char* code = R_STR(rq, "cmd");
if(!code) return;
if(pipe(filedes) == -1)
write(fdm, "exit\n", 5);
ws_close(cl,1000);
}
}
// If data on master side of PTY
if (FD_ISSET(fdm, &fd_in))
{
perror("pipe");
//rc = read(fdm, buff, sizeof(buff));
if ( (rc = read(fdm, buff,sizeof(buff)-1)) > 0)
{
// Send data to websocket
buff[rc] = '\0';
ws_t(cl,buff);
} else
{
if (rc < 0)
{
LOG("Error %d on read standard input. Exit now\n", errno);
write(fdm, "exit\n", 5);
ws_close(cl,1011);
return;
}
pid_t pid = fork();
if(pid == -1)
}
}
//printf("DONE\n");
}
} // End switch
} // End while
}
else
{
perror("folk");
return;
} else if(pid == 0)
{
while ((dup2(filedes[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
close(filedes[1]);
close(filedes[0]);
// executecomand
system(code);
//perror("execl");
struct termios slave_orig_term_settings; // Saved terminal settings
struct termios new_term_settings; // Current terminal settings
// CHILD
// Close the master side of the PTY
close(fdm);
// Save the defaults parameters of the slave side of the PTY
//rc = tcgetattr(fds, &slave_orig_term_settings);
// Set RAW mode on slave side of PTY
//new_term_settings = slave_orig_term_settings;
//cfmakeraw (&new_term_settings);
//tcsetattr (fds, TCSANOW, &new_term_settings);
// The slave side of the PTY becomes the standard input and outputs of the child process
// we use cook mode here
close(0); // Close standard input (current terminal)
close(1); // Close standard output (current terminal)
close(2); // Close standard error (current terminal)
dup(fds); // PTY becomes standard input (0)
dup(fds); // PTY becomes standard output (1)
dup(fds); // PTY becomes standard error (2)
// Now the original file descriptor is useless
close(fds);
// Make the current process a new session leader
setsid();
// As the child is a session leader, set the controlling terminal to be the slave side of the PTY
// (Mandatory for programs like the shell to make them manage correctly their outputs)
ioctl(0, TIOCSCTTY, 1);
//system("/bin/bash");
system("sudo login");
// if Error...
ws_close(cl,1000);
//LOG("%s\n","Terminal exit");
_exit(1);
}
close(filedes[1]);
char buffer[1024];
while (1) {
ssize_t count = read_buf(filedes[0],buffer, sizeof(buffer));
if (count == -1) {
if (errno == EINTR) {
continue;
} else {
perror("read");
return;
}
} else if (count == 0) {
break;
} else {
__t(client,"data:%s\n",buffer);
//handle_child_process_output(buffer, count);
}
}
close(filedes[0]);
wait(0);
free(code);
printf("Child process exit\n");
LOG("%s\n","All processes exit");
}