diff --git a/CMakeLists.txt b/CMakeLists.txt index dc718027..f0e8b0ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,11 +4,11 @@ if(${CMAKE_VERSION} VERSION_LESS "3.12.0") project(ttyd C) set(PROJECT_VERSION_MAJOR "1") set(PROJECT_VERSION_MINOR "7") - set(PROJECT_VERSION_PATCH "4") + set(PROJECT_VERSION_PATCH "5") set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") else() cmake_policy(SET CMP0048 NEW) - project(ttyd VERSION 1.7.4 LANGUAGES C) + project(ttyd VERSION 1.7.5 LANGUAGES C) endif() find_package(Git) diff --git a/README.md b/README.md index e5f7146a..96b74165 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ OPTIONS: -d, --debug Set log level (default: 7) -v, --version Print the version and exit -h, --help Print this text and exit + -z, --idle-session-timeout Maximum period of session inactivity ( Eg: --idle-session-timeout 10 indicates 10 seconds) + -Z, --idle-server-timeout Maximum period of server with no clients connected ( Eg: --idle-server-timeout 10 indicates 10 seconds) ``` Read the example usage on the [wiki](https://github.com/tsl0922/ttyd/wiki/Example-Usage). diff --git a/man/ttyd.1 b/man/ttyd.1 index 96dfea8b..48b62ab3 100644 --- a/man/ttyd.1 +++ b/man/ttyd.1 @@ -145,6 +145,14 @@ Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows -v, --version Print the version and exit +.PP +-z, --idle-session-timeout + Maximum period of session inactivity + +.PP +-Z, --idle-server-timeout + Maximum period of server with no clients connected + .PP -h, --help Print this text and exit diff --git a/man/ttyd.man.md b/man/ttyd.man.md index b587aadd..381e175a 100644 --- a/man/ttyd.man.md +++ b/man/ttyd.man.md @@ -101,6 +101,12 @@ ttyd 1 "September 2016" ttyd "User Manual" -v, --version Print the version and exit + -z, --idle-session-timeout + Maximum period of session inactivity + + -Z, --idle-server-timeout + Maximum period of server with no clients connected + -h, --help Print this text and exit diff --git a/src/protocol.c b/src/protocol.c index 3a53f96f..6db02f14 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -194,6 +194,22 @@ static bool check_auth(struct lws *wsi, struct pss_tty *pss) { return true; } +void *handle_timeout_for_idle_server(void *arg) { + int timeout = server->idle_server_timeout; + while (timeout > 0) { + sleep(1); + if (server->client_count > 0) { + lwsl_notice("New clients are connected within expected time of %d seconds. \n", server->idle_server_timeout); + pthread_exit(NULL); + } + timeout--; + } + lwsl_err("No new clients are connected within the connection timeout period of %d seconds, leading to an exit. \n", server->idle_server_timeout); + force_exit = true; + lws_cancel_service(context); + exit(0); +} + int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct pss_tty *pss = (struct pss_tty *)user; char buf[256]; @@ -229,6 +245,9 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, break; case LWS_CALLBACK_ESTABLISHED: + // set connection_established to 1 which helps in identifying that a client is connected + connection_established = 1; + lwsl_notice("Connection is established.\n"); pss->initialized = false; pss->authenticated = false; pss->wsi = wsi; @@ -278,6 +297,15 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, pss->pty_buf = NULL; pty_resume(pss->process); } + time_t current_time = time(NULL); + double time_difference = difftime(current_time, pss->last_activity); + lwsl_notice("Time elapsed since the last command execution: %.2f seconds\n", time_difference); + + if (time_difference > server->idle_session_timeout && server->idle_session_timeout != -1) { + lwsl_err("Closing the connection as there is no activity since last %d seconds\n", server->idle_session_timeout); + lws_close_reason(wsi, LWS_CLOSE_STATUS_NORMAL, NULL, 0); + return -1; // This will close the connection + } break; case LWS_CALLBACK_RECEIVE: @@ -357,6 +385,7 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, free(pss->buffer); pss->buffer = NULL; } + pss->last_activity = time(NULL); break; case LWS_CALLBACK_CLOSED: @@ -379,12 +408,26 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, } } - if (server->once && server->client_count == 0) { - lwsl_notice("exiting due to the --once option.\n"); - force_exit = true; - lws_cancel_service(context); - exit(0); + if ((server->once || server->idle_server_timeout != -1) && server->client_count == 0) { + lwsl_err("All clients have been disconnected, prompting an exit as a result of the --once/idle-server-timeout set.\n"); + lwsl_err("Waiting for %d seconds before exiting.\n", server->idle_server_timeout); + + if (server->idle_server_timeout != -1) { + pthread_t thread_id; // Thread identifier + int id = 1; // Example data to pass to the thread (can be any data type) + connection_established = 0; + + // Create a new thread + if (pthread_create(&thread_id, NULL, handle_timeout_for_idle_server, &id) != 0) { + fprintf(stderr, "Error creating thread\n"); + return 1; + } + } else { + // If idle_server_timeout is -1, no need to create a thread, just exit immediately + exit(0); + } } + break; default: diff --git a/src/server.c b/src/server.c index 9c8f118e..a93a6026 100644 --- a/src/server.c +++ b/src/server.c @@ -21,6 +21,7 @@ volatile bool force_exit = false; struct lws_context *context; struct server *server; struct endpoints endpoints = {"/ws", "/", "/token", ""}; +int connection_established = 0; extern int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); extern int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); @@ -65,6 +66,8 @@ static const struct option options[] = {{"port", required_argument, NULL, 'p'}, #if LWS_LIBRARY_VERSION_NUMBER >= 4000000 {"ping-interval", required_argument, NULL, 'P'}, #endif + {"idle-session-timeout", required_argument, NULL, 'z'}, + {"idle-server-timeout", required_argument, NULL, 'Z'}, {"ipv6", no_argument, NULL, '6'}, {"ssl", no_argument, NULL, 'S'}, {"ssl-cert", required_argument, NULL, 'C'}, @@ -82,7 +85,7 @@ static const struct option options[] = {{"port", required_argument, NULL, 'p'}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, 0, 0}}; -static const char *opt_string = "p:i:U:c:H:u:g:s:w:I:b:P:6aSC:K:A:Wt:T:Om:oBd:vh"; +static const char *opt_string = "p:i:U:c:H:u:g:s:w:I:b:P:z:Z:6aSC:K:A:Wt:T:Om:oeBd:vh"; static void print_help() { // clang-format off @@ -107,6 +110,8 @@ static void print_help() { " -T, --terminal-type Terminal type to report, default: xterm-256color\n" " -O, --check-origin Do not allow websocket connection from different origin\n" " -m, --max-clients Maximum clients to support (default: 0, no limit)\n" + " -z, --idle-session-timeout Maximum period of session inactivity (eg: --idle-session-timeout 10) \n" + " -Z, --idle-server-timeout Maximum period of server with no clients connected (eg: --idle-server-timeout 10) \n" " -o, --once Accept only one client and exit on disconnection\n" " -B, --browser Open terminal with the default system browser\n" " -I, --index Custom index.html path\n" @@ -152,7 +157,9 @@ static void print_config() { if (server->once) lwsl_notice(" once: true\n"); if (server->index != NULL) lwsl_notice(" custom index.html: %s\n", server->index); if (server->cwd != NULL) lwsl_notice(" working directory: %s\n", server->cwd); - if (!server->writable) lwsl_notice("The --writable option is not set, will start in readonly mode"); + if (!server->writable) lwsl_notice(" The --writable option is not set, will start in readonly mode"); + if (server->idle_session_timeout != 0) lwsl_notice(" Idle Session Timeout: %d seconds\n", server->idle_session_timeout); + if (server->idle_server_timeout != 0) lwsl_notice(" Idle Server Timeout: %d seconds\n", server->idle_server_timeout); } static struct server *server_new(int argc, char **argv, int start) { @@ -295,6 +302,27 @@ static int calc_command_start(int argc, char **argv) { return start; } +#include +#include + +void *handle_timeout_for_server_connection(void *arg) { + time_t start_time = time(NULL); + while (true) { + if (connection_established) { + lwsl_notice("Connection is established within expected time of %d seconds. \n", server->idle_server_timeout); + pthread_exit(NULL); + } + + double elapsed_time = difftime(time(NULL), start_time); + if (elapsed_time >= server->idle_server_timeout) { + lwsl_err("No connections are established within the connection timeout period of %d seconds, leading to an exit. \n", server->idle_server_timeout); + exit(0); + } + + sleep(1); // Sleep only if connection is not established and timeout hasn't occurred yet + } +} + int main(int argc, char **argv) { if (argc == 1) { print_help(); @@ -310,6 +338,9 @@ int main(int argc, char **argv) { int start = calc_command_start(argc, argv); server = server_new(argc, argv, start); + server->idle_server_timeout = -1; + server->idle_session_timeout = -1; + struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); info.port = 7681; @@ -367,6 +398,22 @@ int main(int argc, char **argv) { case 'o': server->once = true; break; + case 'z': + int idle_session_interval = parse_int("idle-session-timeout", optarg); + if (idle_session_interval < 0) { + fprintf(stderr, "ttyd: invalid idle session timeout: %s\n", optarg); + return -1; + } + server->idle_session_timeout = idle_session_interval; + break; + case 'Z': + int idle_server_interval = parse_int("idle-server-timeout", optarg); + if (idle_server_interval < 0) { + fprintf(stderr, "ttyd: invalid idle server timeout: %s\n", optarg); + return -1; + } + server->idle_server_timeout = idle_server_interval; + break; case 'B': browser = true; break; @@ -602,6 +649,18 @@ int main(int argc, char **argv) { uv_signal_start(&signals[i], signal_cb, sig_nums[i]); } + if (server->idle_server_timeout != -1) { + pthread_t thread_id; // Thread identifier + int id = 1; // Example data to pass to the thread (can be any data type) + connection_established = 0; + + // Create a new thread + if (pthread_create(&thread_id, NULL, handle_timeout_for_server_connection, &id) != 0) { + fprintf(stderr, "Error creating thread\n"); + return 1; + } +} + lws_service(context, 0); for (int i = 0; i < sig_count; i++) { diff --git a/src/server.h b/src/server.h index 4a659b0e..12c18fb6 100644 --- a/src/server.h +++ b/src/server.h @@ -54,6 +54,8 @@ struct pss_tty { pty_buf_t *pty_buf; int lws_close_status; + + time_t last_activity; }; typedef struct { @@ -63,6 +65,8 @@ typedef struct { struct server { int client_count; // client count + int idle_session_timeout;// idle session timeout + int idle_server_timeout; // idle server timeout char *prefs_json; // client preferences char *credential; // encoded basic auth credential char *auth_header; // header name used for auth proxy diff --git a/src/utils.h b/src/utils.h index 826bb946..28723127 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,6 +1,8 @@ #ifndef TTYD_UTIL_H #define TTYD_UTIL_H +extern int connection_established; + #define container_of(ptr, type, member) \ ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \