From: Shuanglei Tao Date: Sat, 4 Feb 2017 12:46:16 +0000 (+0800) Subject: Execute command after authentication X-Git-Url: http://git.prime8.dev/?a=commitdiff_plain;h=ffdf56e440c802ea2d7a4ed38304c54700021fc1;p=ttyd.git Execute command after authentication --- diff --git a/README.md b/README.md index 69eb12f..9700adf 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,11 @@ OPTIONS: --ssl-cert, -C SSL certificate file path --ssl-key, -K SSL key file path --ssl-ca, -A SSL CA file path for client certificate verification - --debug, -d Set log level (0-9, default: 7) + --debug, -d Set log level (default: 7) --version, -v Print the version and exit --help, -h Print this text and exit + +Visit https://github.com/tsl0922/ttyd to get more information and report bugs. ``` ## Example Usage diff --git a/html/js/app.js b/html/js/app.js index c9127e6..da8ddad 100644 --- a/html/js/app.js +++ b/html/js/app.js @@ -2,6 +2,7 @@ var terminalContainer = document.getElementById('terminal-container'), httpsEnabled = window.location.protocol == "https:", url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws', + authToken = (typeof tty_auth_token !== 'undefined') ? tty_auth_token : null, protocols = ["tty"], autoReconnect = -1, term, pingTimer, wsError; @@ -10,10 +11,9 @@ var ws = new WebSocket(url, protocols); ws.onopen = function(event) { + console.log("Websocket connection opened"); wsError = false; - if (typeof tty_auth_token !== 'undefined') { - ws.send(JSON.stringify({AuthToken: tty_auth_token})); - } + ws.send(JSON.stringify({AuthToken: authToken})); pingTimer = setInterval(sendPing, 30 * 1000, ws); if (typeof term !== 'undefined') { @@ -22,7 +22,7 @@ term = new Terminal(); - term.on('resize', function (size) { + term.on('resize', function(size) { if (ws.readyState === WebSocket.OPEN) { ws.send("2" + JSON.stringify({columns: size.cols, rows: size.rows})); } @@ -30,22 +30,26 @@ term.showOverlay(size.cols + 'x' + size.rows); }, 500); }); + term.on("data", function(data) { if (ws.readyState === WebSocket.OPEN) { ws.send("0" + data); } }); - window.onresize = function(event) { + + term.on('open', function() { + window.onresize = function(event) { + term.fit(); + }; term.fit(); - }; + term.focus(); + }); while (terminalContainer.firstChild) { terminalContainer.removeChild(terminalContainer.firstChild); } term.open(terminalContainer); - term.fit(); - term.focus(); }; ws.onmessage = function(event) { @@ -68,12 +72,13 @@ break; case '4': autoReconnect = JSON.parse(data); - console.log("Enabling reconnect: " + autoReconnect + " seconds") + console.log("Enabling reconnect: " + autoReconnect + " seconds"); break; } }; ws.onclose = function(event) { + console.log("Websocket connection closed with code: " + event.code); if (term) { term.off('data'); term.off('resize'); @@ -86,11 +91,6 @@ setTimeout(openWs, autoReconnect * 1000); } }; - - ws.onerror = function(event) { - wsError = true; - term.showOverlay("Websocket handshake failed", null); - }; }; var sendPing = function(ws) { diff --git a/src/http.c b/src/http.c index 8b63e85..e704bed 100644 --- a/src/http.c +++ b/src/http.c @@ -50,21 +50,19 @@ check_auth(struct lws *wsi) { int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { unsigned char buffer[4096 + LWS_PRE], *p, *end; - char buf[256]; + char buf[256], name[100], rip[50]; switch (reason) { case LWS_CALLBACK_HTTP: - { - char name[100], rip[50]; - lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); - lwsl_notice("HTTP connect from %s (%s), path: %s\n", name, rip, in); - } - - if (len < 1) { + // only GET method is allowed + if (!lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) || len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } + lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); + lwsl_notice("HTTP %s - %s (%s)\n", in, rip, name); + switch (check_auth(wsi)) { case 0: break; @@ -75,10 +73,6 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, voi return 1; } - // if a legal POST URL, let it continue and accept data - if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) - return 0; - p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; @@ -130,7 +124,6 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, voi return 1; } goto try_to_reuse; - case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: if (!len || (SSL_get_verify_result((SSL *) in) != X509_V_OK)) { int err = X509_STORE_CTX_get_error((X509_STORE_CTX *) user); @@ -140,7 +133,6 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, voi return 1; } break; - default: break; } diff --git a/src/index.html b/src/index.html index fb3ab57..a0c65aa 100644 --- a/src/index.html +++ b/src/index.html @@ -16,6 +16,6 @@ break;case 33:t.shiftKey?e.scrollDisp=-(this.rows-1):e.key="[5~";break;case 34:
- + diff --git a/src/protocol.c b/src/protocol.c index 278240a..382f4a7 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -1,18 +1,5 @@ #include "server.h" -// client message -#define INPUT '0' -#define PING '1' -#define RESIZE_TERMINAL '2' -#define JSON_DATA '{' - -// server message -#define OUTPUT '0' -#define PONG '1' -#define SET_WINDOW_TITLE '2' -#define SET_PREFERENCES '3' -#define SET_RECONNECT '4' - #define BUF_SIZE 1024 int @@ -42,30 +29,29 @@ send_initial_message(struct lws *wsi) { return 0; } -struct winsize * -parse_window_size(const char *json) { +bool +parse_window_size(const char *json, struct winsize *size) { int columns, rows; json_object *obj = json_tokener_parse(json); struct json_object *o = NULL; if (!json_object_object_get_ex(obj, "columns", &o)) { lwsl_err("columns field not exists!\n"); - return NULL; + return false; } columns = json_object_get_int(o); if (!json_object_object_get_ex(obj, "rows", &o)) { lwsl_err("rows field not exists!\n"); - return NULL; + return false; } rows = json_object_get_int(o); json_object_put(obj); - struct winsize *size = xmalloc(sizeof(struct winsize)); memset(size, 0, sizeof(struct winsize)); size->ws_col = (unsigned short) columns; size->ws_row = (unsigned short) rows; - return size; + return true; } bool @@ -93,11 +79,9 @@ check_host_origin(struct lws *wsi) { void tty_client_destroy(struct tty_client *client) { - if (client->exit || client->pid <= 0) + if (!client->running || client->pid <= 0) return; - - // stop event loop - client->exit = true; + client->running = false; // kill process and free resource lwsl_notice("sending %s to process %d\n", server->sig_name, client->pid); @@ -114,7 +98,7 @@ tty_client_destroy(struct tty_client *client) { if (client->buffer != NULL) free(client->buffer); - // remove from clients list + // remove from client list pthread_mutex_lock(&server->lock); LIST_REMOVE(client, list); server->client_count--; @@ -150,14 +134,16 @@ thread_run_command(void *args) { lwsl_notice("started process, pid: %d\n", pid); client->pid = pid; client->pty = pty; + client->running = true; + if (client->size.ws_row > 0 && client->size.ws_col > 0) + ioctl(client->pty, TIOCSWINSZ, &client->size); - while (!client->exit) { + while (client->running) { FD_ZERO (&des_set); FD_SET (pty, &des_set); - if (select(pty + 1, &des_set, NULL, NULL, NULL) < 0) { + if (select(pty + 1, &des_set, NULL, NULL, NULL) < 0) break; - } if (FD_ISSET (pty, &des_set)) { memset(buf, 0, BUF_SIZE); @@ -173,7 +159,6 @@ thread_run_command(void *args) { pthread_mutex_unlock(&client->lock); } } - tty_client_destroy(client); break; } @@ -184,22 +169,27 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct tty_client *client = (struct tty_client *) user; - struct winsize *size; + char buf[256]; switch (reason) { case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: if (server->once && server->client_count > 0) { - lwsl_notice("refuse to serve new client due to the --once option.\n"); - return -1; + lwsl_warn("refuse to serve WS client due to the --once option.\n"); + return 1; + } + if (lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI) <= 0 || strcmp(buf, WS_PATH)) { + lwsl_warn("refuse to serve WS client for illegal ws path: %s\n", buf); + return 1; } + if (server->check_origin && !check_host_origin(wsi)) { - lwsl_notice("refuse to serve new client from different origin due to the --check-origin option.\n"); - return -1; + lwsl_warn("refuse to serve WS client from different origin due to the --check-origin option.\n"); + return 1; } break; case LWS_CALLBACK_ESTABLISHED: - client->exit = false; + client->running = false; client->initialized = false; client->authenticated = false; client->wsi = wsi; @@ -208,17 +198,14 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, client->hostname, sizeof(client->hostname), client->address, sizeof(client->address)); STAILQ_INIT(&client->queue); - if (pthread_create(&client->thread, NULL, thread_run_command, client) != 0) { - lwsl_err("pthread_create\n"); - return -1; - } pthread_mutex_lock(&server->lock); LIST_INSERT_HEAD(&server->clients, client, list); server->client_count++; pthread_mutex_unlock(&server->lock); + lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI); - lwsl_notice("client connected from %s (%s), total: %d\n", client->hostname, client->address, server->client_count); + lwsl_notice("WS %s - %s (%s), clients: %d\n", buf, client->address, client->hostname, server->client_count); break; case LWS_CALLBACK_SERVER_WRITEABLE: @@ -280,8 +267,8 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, // check auth if (server->credential != NULL && !client->authenticated && command != JSON_DATA) { - lwsl_notice("websocket authentication failed\n"); - return -1; + lwsl_warn("WS client not authenticated\n"); + return 1; } // check if there are more fragmented messages @@ -291,6 +278,8 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, switch (command) { case INPUT: + if (client->pty == 0) + break; if (server->readonly) return 0; if (write(client->pty, client->buffer + 1, client->len - 1) < client->len - 1) { @@ -308,32 +297,34 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, } break; case RESIZE_TERMINAL: - size = parse_window_size(client->buffer + 1); - if (size != NULL) { - if (ioctl(client->pty, TIOCSWINSZ, size) == -1) { + if (parse_window_size(client->buffer + 1, &client->size) && client->pty > 0) { + if (ioctl(client->pty, TIOCSWINSZ, &client->size) == -1) { lwsl_err("ioctl TIOCSWINSZ: %d (%s)\n", errno, strerror(errno)); } - free(size); } break; case JSON_DATA: - if (server->credential == NULL) + if (client->pid > 0) break; - { + if (server->credential != NULL) { json_object *obj = json_tokener_parse(client->buffer); struct json_object *o = NULL; if (json_object_object_get_ex(obj, "AuthToken", &o)) { const char *token = json_object_get_string(o); - if (strcmp(token, server->credential)) { - lwsl_notice("websocket authentication failed with token: %s\n", token); - return -1; + if (token == NULL || strcmp(token, server->credential)) { + lwsl_warn("WS authentication failed with token: %s\n", token); + return 1; } } client->authenticated = true; } + if (pthread_create(&client->thread, NULL, thread_run_command, client) != 0) { + lwsl_err("pthread_create\n"); + return 1; + } break; default: - lwsl_notice("unknown message type: %c\n", command); + lwsl_warn("unknown message type: %c\n", command); break; } @@ -345,7 +336,7 @@ callback_tty(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_CLOSED: tty_client_destroy(client); - lwsl_notice("client disconnected from %s (%s), total: %d\n", client->hostname, client->address, server->client_count); + lwsl_notice("WS closed from %s (%s), clients: %d\n", client->address, client->hostname, server->client_count); if (server->once && server->client_count == 0) { lwsl_notice("exiting due to the --once option.\n"); force_exit = true; diff --git a/src/server.c b/src/server.c index cf5e94d..367ced9 100644 --- a/src/server.c +++ b/src/server.c @@ -67,9 +67,10 @@ void print_help() { " --ssl-cert, -C SSL certificate file path\n" " --ssl-key, -K SSL key file path\n" " --ssl-ca, -A SSL CA file path for client certificate verification\n" - " --debug, -d Set log level (0-9, default: 7)\n" + " --debug, -d Set log level (default: 7)\n" " --version, -v Print the version and exit\n" - " --help, -h Print this text and exit\n", + " --help, -h Print this text and exit\n\n" + "Visit https://github.com/tsl0922/ttyd to get more information and report bugs.\n", TTYD_VERSION ); } @@ -215,7 +216,7 @@ main(int argc, char **argv) { info.extensions = extensions; info.timeout_secs = 5; - int debug_level = 7; + int debug_level = LLL_ERR | LLL_WARN | LLL_NOTICE; char iface[128] = ""; bool ssl = false; char cert_path[1024] = ""; @@ -371,6 +372,7 @@ main(int argc, char **argv) { #endif } } + if (ssl) { info.ssl_cert_filepath = cert_path; info.ssl_private_key_filepath = key_path; @@ -395,16 +397,8 @@ main(int argc, char **argv) { #endif } - signal(SIGINT, sig_handler); // ^C - signal(SIGTERM, sig_handler); // kill - - context = lws_create_context(&info); - if (context == NULL) { - lwsl_err("libwebsockets init failed\n"); - return 1; - } - - lwsl_notice("TTY configuration:\n"); + lwsl_notice("ttyd %s (libwebsockets %s)\n", TTYD_VERSION, LWS_LIBRARY_VERSION); + lwsl_notice("tty configuration:\n"); if (server->credential != NULL) lwsl_notice(" credential: %s\n", server->credential); lwsl_notice(" start command: %s\n", server->command); @@ -420,13 +414,27 @@ main(int argc, char **argv) { lwsl_notice(" custom index.html: %s\n", server->index); } + signal(SIGINT, sig_handler); // ^C + signal(SIGTERM, sig_handler); // kill + + context = lws_create_context(&info); + if (context == NULL) { + lwsl_err("libwebsockets init failed\n"); + return 1; + } + if (server->socket_path != NULL) { + lwsl_notice("listening on socket %s\n", server->socket_path); + } else { + lwsl_notice("listening on port %d\n", info.port); + } + // libwebsockets main loop while (!force_exit) { pthread_mutex_lock(&server->lock); if (!LIST_EMPTY(&server->clients)) { struct tty_client *client; LIST_FOREACH(client, &server->clients, list) { - if (!STAILQ_EMPTY(&client->queue)) { + if (client->running && !STAILQ_EMPTY(&client->queue)) { lws_callback_on_writable(client->wsi); } } diff --git a/src/server.h b/src/server.h index 6951b70..257ca2f 100644 --- a/src/server.h +++ b/src/server.h @@ -35,6 +35,22 @@ #include "utils.h" +// client message +#define INPUT '0' +#define PING '1' +#define RESIZE_TERMINAL '2' +#define JSON_DATA '{' + +// server message +#define OUTPUT '0' +#define PONG '1' +#define SET_WINDOW_TITLE '2' +#define SET_PREFERENCES '3' +#define SET_RECONNECT '4' + +// websocket url path +#define WS_PATH "/ws" + extern volatile bool force_exit; extern struct lws_context *context; extern struct tty_server *server; @@ -46,13 +62,14 @@ struct pty_data { }; struct tty_client { - bool exit; + bool running; bool initialized; bool authenticated; char hostname[100]; char address[50]; struct lws *wsi; + struct winsize size; char *buffer; size_t len; int pid;