diff options
Diffstat (limited to 'src/server.c')
-rw-r--r-- | src/server.c | 308 |
1 files changed, 150 insertions, 158 deletions
diff --git a/src/server.c b/src/server.c index 70b0864..f9113af 100644 --- a/src/server.c +++ b/src/server.c @@ -22,75 +22,66 @@ struct lws_context *context; struct server *server; struct endpoints endpoints = {"/ws", "/", "/token", ""}; -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); +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); // websocket protocols -static const struct lws_protocols protocols[] = { - {"http-only", callback_http, sizeof(struct pss_http), 0}, - {"tty", callback_tty, sizeof(struct pss_tty), 0}, - {NULL, NULL, 0, 0}}; +static const struct lws_protocols protocols[] = {{"http-only", callback_http, sizeof(struct pss_http), 0}, + {"tty", callback_tty, sizeof(struct pss_tty), 0}, + {NULL, NULL, 0, 0}}; #ifndef LWS_WITHOUT_EXTENSIONS // websocket extensions static const struct lws_extension extensions[] = { - {"permessage-deflate", lws_extension_callback_pm_deflate, - "permessage-deflate"}, + {"permessage-deflate", lws_extension_callback_pm_deflate, "permessage-deflate"}, {"deflate-frame", lws_extension_callback_pm_deflate, "deflate_frame"}, {NULL, NULL, NULL}}; #endif #if LWS_LIBRARY_VERSION_NUMBER >= 4000000 -static const uint32_t backoff_ms[] = { 1000, 2000, 3000, 4000, 5000 }; +static const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000}; static lws_retry_bo_t retry = { - .retry_ms_table = backoff_ms, - .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms), - .conceal_count = LWS_ARRAY_SIZE(backoff_ms), - .secs_since_valid_ping = 300, - .secs_since_valid_hangup = 300 + 7, - .jitter_percent = 0, - }; + .retry_ms_table = backoff_ms, + .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms), + .conceal_count = LWS_ARRAY_SIZE(backoff_ms), + .secs_since_valid_ping = 5, + .secs_since_valid_hangup = 10, + .jitter_percent = 0, +}; #endif // command line options -static const struct option options[] = { - {"port", required_argument, NULL, 'p'}, - {"interface", required_argument, NULL, 'i'}, - {"credential", required_argument, NULL, 'c'}, - {"uid", required_argument, NULL, 'u'}, - {"gid", required_argument, NULL, 'g'}, - {"signal", required_argument, NULL, 's'}, - {"index", required_argument, NULL, 'I'}, - {"base-path", required_argument, NULL, 'b'}, +static const struct option options[] = {{"port", required_argument, NULL, 'p'}, + {"interface", required_argument, NULL, 'i'}, + {"credential", required_argument, NULL, 'c'}, + {"auth-header", required_argument, NULL, 'H'}, + {"uid", required_argument, NULL, 'u'}, + {"gid", required_argument, NULL, 'g'}, + {"signal", required_argument, NULL, 's'}, + {"cwd", required_argument, NULL, 'w'}, + {"index", required_argument, NULL, 'I'}, + {"base-path", required_argument, NULL, 'b'}, #if LWS_LIBRARY_VERSION_NUMBER >= 4000000 - {"ping-interval", required_argument, NULL, 'P'}, -#endif - {"ipv6", no_argument, NULL, '6'}, - {"ssl", no_argument, NULL, 'S'}, - {"ssl-cert", required_argument, NULL, 'C'}, - {"ssl-key", required_argument, NULL, 'K'}, - {"ssl-ca", required_argument, NULL, 'A'}, - {"url-arg", no_argument, NULL, 'a'}, - {"readonly", no_argument, NULL, 'R'}, - {"terminal-type", required_argument, NULL, 'T'}, - {"client-option", required_argument, NULL, 't'}, - {"check-origin", no_argument, NULL, 'O'}, - {"max-clients", required_argument, NULL, 'm'}, - {"once", no_argument, NULL, 'o'}, - {"browser", no_argument, NULL, 'B'}, - {"debug", required_argument, NULL, 'd'}, - {"version", no_argument, NULL, 'v'}, - {"help", no_argument, NULL, 'h'}, - {NULL, 0, 0, 0}}; - -#if LWS_LIBRARY_VERSION_NUMBER < 4000000 -static const char *opt_string = "p:i:c:u:g:s:I:b:6aSC:K:A:Rt:T:Om:oBd:vh"; -#endif -#if LWS_LIBRARY_VERSION_NUMBER >= 4000000 -static const char *opt_string = "p:i:c:u:g:s:I:b:P:6aSC:K:A:Rt:T:Om:oBd:vh"; + {"ping-interval", required_argument, NULL, 'P'}, #endif + {"ipv6", no_argument, NULL, '6'}, + {"ssl", no_argument, NULL, 'S'}, + {"ssl-cert", required_argument, NULL, 'C'}, + {"ssl-key", required_argument, NULL, 'K'}, + {"ssl-ca", required_argument, NULL, 'A'}, + {"url-arg", no_argument, NULL, 'a'}, + {"readonly", no_argument, NULL, 'R'}, + {"terminal-type", required_argument, NULL, 'T'}, + {"client-option", required_argument, NULL, 't'}, + {"check-origin", no_argument, NULL, 'O'}, + {"max-clients", required_argument, NULL, 'm'}, + {"once", no_argument, NULL, 'o'}, + {"browser", no_argument, NULL, 'B'}, + {"debug", required_argument, NULL, 'd'}, + {"version", no_argument, NULL, 'v'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, 0, 0}}; +static const char *opt_string = "p:i:c:H:u:g:s:w:I:b:P:6aSC:K:A:Rt:T:Om:oBd:vh"; static void print_help() { // clang-format off @@ -102,10 +93,12 @@ static void print_help() { "OPTIONS:\n" " -p, --port Port to listen (default: 7681, use `0` for random port)\n" " -i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)\n" - " -c, --credential Credential for Basic Authentication (format: username:password)\n" + " -c, --credential Credential for basic authentication (format: username:password)\n" + " -H, --auth-header HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication\n" " -u, --uid User id to run with\n" " -g, --gid Group id to run with\n" " -s, --signal Signal to send to the command when exit it (default: 1, SIGHUP)\n" + " -w, --cwd Working directory to be set for the child program\n" " -a, --url-arg Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)\n" " -R, --readonly Do not allow clients to write to the TTY\n" " -t, --client-option Send option to client (format: key=value), repeat to add more options\n" @@ -115,9 +108,9 @@ static void print_help() { " -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" - " -b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here)\n" + " -b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)\n" #if LWS_LIBRARY_VERSION_NUMBER >= 4000000 - " -P, --ping-interval Websocket ping interval(sec) (default: 300)\n" + " -P, --ping-interval Websocket ping interval(sec) (default: 5)\n" #endif #ifdef LWS_WITH_IPV6 " -6, --ipv6 Enable IPv6 support\n" @@ -137,6 +130,28 @@ static void print_help() { // clang-format on } +static void print_config() { + lwsl_notice("tty configuration:\n"); + if (server->credential != NULL) lwsl_notice(" credential: %s\n", server->credential); + lwsl_notice(" start command: %s\n", server->command); + lwsl_notice(" close signal: %s (%d)\n", server->sig_name, server->sig_code); + lwsl_notice(" terminal type: %s\n", server->terminal_type); + if (endpoints.parent[0]) { + lwsl_notice("endpoints:\n"); + lwsl_notice(" base-path: %s\n", endpoints.parent); + lwsl_notice(" index : %s\n", endpoints.index); + lwsl_notice(" token : %s\n", endpoints.token); + lwsl_notice(" websocket: %s\n", endpoints.ws); + } + if (server->auth_header != NULL) lwsl_notice(" auth header: %s\n", server->auth_header); + if (server->check_origin) lwsl_notice(" check origin: true\n"); + if (server->url_arg) lwsl_notice(" allow url arg: true\n"); + if (server->readonly) lwsl_notice(" readonly: true\n"); + if (server->max_clients > 0) lwsl_notice(" max clients: %d\n", server->max_clients); + if (server->once) lwsl_notice(" once: true\n"); + if (server->index != NULL) lwsl_notice(" custom index.html: %s\n", server->index); +} + static struct server *server_new(int argc, char **argv, int start) { struct server *ts; size_t cmd_len = 0; @@ -144,7 +159,6 @@ static struct server *server_new(int argc, char **argv, int start) { ts = xmalloc(sizeof(struct server)); memset(ts, 0, sizeof(struct server)); - LIST_INIT(&ts->procs); ts->client_count = 0; ts->sig_code = SIGHUP; sprintf(ts->terminal_type, "%s", "xterm-256color"); @@ -167,7 +181,8 @@ static struct server *server_new(int argc, char **argv, int start) { ts->command = xmalloc(cmd_len + 1); char *ptr = ts->command; for (int i = 0; i < cmd_argc; i++) { - ptr = stpcpy(ptr, ts->argv[i]); + size_t len = strlen(ts->argv[i]); + ptr = memcpy(ptr, ts->argv[i], len + 1) + len; if (i != cmd_argc - 1) { *ptr++ = ' '; } @@ -176,8 +191,6 @@ static struct server *server_new(int argc, char **argv, int start) { ts->loop = xmalloc(sizeof *ts->loop); uv_loop_init(ts->loop); - uv_signal_init(ts->loop, &ts->watcher); - ts->watcher.data = &ts->procs; return ts; } @@ -185,22 +198,25 @@ static struct server *server_new(int argc, char **argv, int start) { static void server_free(struct server *ts) { if (ts == NULL) return; if (ts->credential != NULL) free(ts->credential); + if (ts->auth_header != NULL) free(ts->auth_header); if (ts->index != NULL) free(ts->index); + if (ts->cwd != NULL) free(ts->cwd); free(ts->command); free(ts->prefs_json); - int i = 0; - do { - free(ts->argv[i++]); - } while (ts->argv[i] != NULL); + + char **p = ts->argv; + for (; *p; p++) free(*p); free(ts->argv); + if (strlen(ts->socket_path) > 0) { struct stat st; if (!stat(ts->socket_path, &st)) { unlink(ts->socket_path); } } - uv_signal_stop(&ts->watcher); + uv_loop_close(ts->loop); + free(ts->loop); free(ts); } @@ -212,8 +228,7 @@ static void signal_cb(uv_signal_t *watcher, int signum) { case SIGINT: case SIGTERM: get_sig_name(watcher->signum, sig_name, sizeof(sig_name)); - lwsl_notice("received signal: %s (%d), exiting...\n", sig_name, - watcher->signum); + lwsl_notice("received signal: %s (%d), exiting...\n", sig_name, watcher->signum); break; default: signal(SIGABRT, SIG_DFL); @@ -222,14 +237,22 @@ static void signal_cb(uv_signal_t *watcher, int signum) { if (force_exit) exit(EXIT_FAILURE); force_exit = true; + lws_cancel_service(context); -#if LWS_LIBRARY_VERSION_MAJOR >= 3 uv_stop(server->loop); + lwsl_notice("send ^C to force exit.\n"); -#else - lws_libuv_stop(context); - exit(EXIT_SUCCESS); -#endif +} + +static int parse_int(char *name, char *str) { + char *endptr; + errno = 0; + long val = strtol(str, &endptr, 0); + if (errno != 0 || endptr == str) { + fprintf(stderr, "ttyd: invalid value for %s: %s\n", name, str); + exit(EXIT_FAILURE); + } + return (int)val; } static int calc_command_start(int argc, char **argv) { @@ -274,6 +297,12 @@ int main(int argc, char **argv) { print_help(); return 0; } +#ifdef _WIN32 + if (!conpty_init()) { + fprintf(stderr, "ERROR: ConPTY init failed! Make sure you are on Windows 10 1809 or later."); + return 1; + } +#endif int start = calc_command_start(argc, argv); server = server_new(argc, argv, start); @@ -286,8 +315,7 @@ int main(int argc, char **argv) { info.gid = -1; info.uid = -1; info.max_http_header_pool = 16; - info.options = LWS_SERVER_OPTION_LIBUV | LWS_SERVER_OPTION_VALIDATE_UTF8 | - LWS_SERVER_OPTION_DISABLE_IPV6; + info.options = LWS_SERVER_OPTION_LIBUV | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_DISABLE_IPV6; #ifndef LWS_WITHOUT_EXTENSIONS info.extensions = extensions; #endif @@ -314,7 +342,7 @@ int main(int argc, char **argv) { printf("ttyd version %s\n", TTYD_VERSION); return 0; case 'd': - debug_level = atoi(optarg); + debug_level = parse_int("debug", optarg); break; case 'a': server->url_arg = true; @@ -326,7 +354,7 @@ int main(int argc, char **argv) { server->check_origin = true; break; case 'm': - server->max_clients = atoi(optarg); + server->max_clients = parse_int("max-clients", optarg); break; case 'o': server->once = true; @@ -335,7 +363,7 @@ int main(int argc, char **argv) { browser = true; break; case 'p': - info.port = atoi(optarg); + info.port = parse_int("port", optarg); if (info.port < 0) { fprintf(stderr, "ttyd: invalid port: %s\n", optarg); return -1; @@ -347,18 +375,21 @@ int main(int argc, char **argv) { break; case 'c': if (strchr(optarg, ':') == NULL) { - fprintf(stderr, - "ttyd: invalid credential, format: username:password\n"); + fprintf(stderr, "ttyd: invalid credential, format: username:password\n"); return -1; } - server->credential = - base64_encode((const unsigned char *)optarg, strlen(optarg)); + char b64_text[256]; + lws_b64_encode_string(optarg, strlen(optarg), b64_text, sizeof(b64_text)); + server->credential = strdup(b64_text); + break; + case 'H': + server->auth_header = strdup(optarg); break; case 'u': - info.uid = atoi(optarg); + info.uid = parse_int("uid", optarg); break; case 'g': - info.gid = atoi(optarg); + info.gid = parse_int("gid", optarg); break; case 's': { int sig = get_sig(optarg); @@ -370,6 +401,9 @@ int main(int argc, char **argv) { return -1; } } break; + case 'w': + server->cwd = strdup(optarg); + break; case 'I': if (!strncmp(optarg, "~/", 2)) { const char *home = getenv("HOME"); @@ -380,13 +414,11 @@ int main(int argc, char **argv) { } struct stat st; if (stat(server->index, &st) == -1) { - fprintf(stderr, "Can not stat index.html: %s, error: %s\n", - server->index, strerror(errno)); + fprintf(stderr, "Can not stat index.html: %s, error: %s\n", server->index, strerror(errno)); return -1; } if (S_ISDIR(st.st_mode)) { - fprintf(stderr, "Invalid index.html path: %s, is it a dir?\n", - server->index); + fprintf(stderr, "Invalid index.html path: %s, is it a dir?\n", server->index); return -1; } break; @@ -403,15 +435,16 @@ int main(int argc, char **argv) { #undef sc } break; #if LWS_LIBRARY_VERSION_NUMBER >= 4000000 - case 'P': - if (atoi(optarg) <= 0) { + case 'P': { + int interval = parse_int("ping-interval", optarg); + if (interval <= 0) { fprintf(stderr, "ttyd: invalid ping interval: %s\n", optarg); return -1; } - retry.secs_since_valid_ping = atoi(optarg); - retry.secs_since_valid_hangup = atoi(optarg) + 7; + retry.secs_since_valid_ping = interval; + retry.secs_since_valid_hangup = interval + 7; info.retry_and_idle_policy = &retry; - break; + } break; #endif case '6': info.options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6); @@ -434,8 +467,7 @@ int main(int argc, char **argv) { break; #endif case 'T': - strncpy(server->terminal_type, optarg, - sizeof(server->terminal_type) - 1); + strncpy(server->terminal_type, optarg, sizeof(server->terminal_type) - 1); server->terminal_type[sizeof(server->terminal_type) - 1] = '\0'; break; case '?': @@ -443,26 +475,19 @@ int main(int argc, char **argv) { case 't': optind--; for (; optind < start && *argv[optind] != '-'; optind++) { - char *option = strdup(optarg); + char *option = optarg; char *key = strsep(&option, "="); if (key == NULL) { - fprintf(stderr, - "ttyd: invalid client option: %s, format: key=value\n", - optarg); + fprintf(stderr, "ttyd: invalid client option: %s, format: key=value\n", optarg); return -1; } char *value = strsep(&option, "="); if (value == NULL) { - fprintf(stderr, - "ttyd: invalid client option: %s, format: key=value\n", - optarg); + fprintf(stderr, "ttyd: invalid client option: %s, format: key=value\n", optarg); return -1; } - free(option); struct json_object *obj = json_tokener_parse(value); - json_object_object_add( - client_prefs, key, - obj != NULL ? obj : json_object_new_string(value)); + json_object_object_add(client_prefs, key, obj != NULL ? obj : json_object_new_string(value)); } break; default: @@ -480,13 +505,11 @@ int main(int argc, char **argv) { lws_set_log_level(debug_level, NULL); -#if LWS_LIBRARY_VERSION_MAJOR >= 2 char server_hdr[128] = ""; - sprintf(server_hdr, "ttyd/%s (libwebsockets/%s)", TTYD_VERSION, - LWS_LIBRARY_VERSION); + sprintf(server_hdr, "ttyd/%s (libwebsockets/%s)", TTYD_VERSION, LWS_LIBRARY_VERSION); info.server_string = server_hdr; -#endif -#if LWS_LIBRARY_VERSION_NUMBER >= 2001000 && LWS_LIBRARY_VERSION_NUMBER < 4000000 + +#if LWS_LIBRARY_VERSION_NUMBER < 4000000 info.ws_ping_pong_interval = 5; #endif @@ -495,11 +518,10 @@ int main(int argc, char **argv) { if (endswith(info.iface, ".sock") || endswith(info.iface, ".socket")) { #if defined(LWS_USE_UNIX_SOCK) || defined(LWS_WITH_UNIX_SOCK) info.options |= LWS_SERVER_OPTION_UNIX_SOCK; - info.port = 0; // warmcat/libwebsockets#1985 + info.port = 0; // warmcat/libwebsockets#1985 strncpy(server->socket_path, info.iface, sizeof(server->socket_path) - 1); #else - fprintf(stderr, - "libwebsockets is not compiled with UNIX domain socket support"); + fprintf(stderr, "libwebsockets is not compiled with UNIX domain socket support"); return -1; #endif } @@ -509,46 +531,29 @@ int main(int argc, char **argv) { if (ssl) { info.ssl_cert_filepath = cert_path; info.ssl_private_key_filepath = key_path; - if (strlen(ca_path) > 0) + if (strlen(ca_path) > 0) { info.ssl_ca_filepath = ca_path; info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT; -#if LWS_LIBRARY_VERSION_MAJOR >= 2 + } info.options |= LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS; -#endif } #endif - 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); - lwsl_notice(" close signal: %s (%d)\n", server->sig_name, server->sig_code); - lwsl_notice(" terminal type: %s\n", server->terminal_type); - if (endpoints.parent[0]) { - lwsl_notice("endpoints:\n"); - lwsl_notice(" base-path: %s\n", endpoints.parent); - lwsl_notice(" index : %s\n", endpoints.index); - lwsl_notice(" token : %s\n", endpoints.token); - lwsl_notice(" websocket: %s\n", endpoints.ws); - } - if (server->check_origin) lwsl_notice(" check origin: true\n"); - if (server->url_arg) lwsl_notice(" allow url arg: true\n"); - if (server->readonly) lwsl_notice(" readonly: true\n"); - if (server->max_clients > 0) - lwsl_notice(" max clients: %d\n", server->max_clients); - if (server->once) lwsl_notice(" once: true\n"); - if (server->index != NULL) { - lwsl_notice(" custom index.html: %s\n", server->index); + lwsl_notice("ttyd %s (libwebsockets %s)\n", TTYD_VERSION, LWS_LIBRARY_VERSION); + print_config(); + + // lws custom header requires lower case name, and terminating : + if (server->auth_header != NULL) { + size_t auth_header_len = strlen(server->auth_header); + server->auth_header = xrealloc(server->auth_header, auth_header_len + 2); + strcat(server->auth_header + auth_header_len, ":"); + lowercase(server->auth_header); } -#if LWS_LIBRARY_VERSION_MAJOR >= 3 void *foreign_loops[1]; foreign_loops[0] = server->loop; info.foreign_loops = foreign_loops; info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; -#endif context = lws_create_context(&info); if (context == NULL) { @@ -556,16 +561,12 @@ int main(int argc, char **argv) { return 1; } -#if LWS_LIBRARY_VERSION_MAJOR >= 3 struct lws_vhost *vhost = lws_create_vhost(context, &info); if (vhost == NULL) { lwsl_err("libwebsockets vhost creation failed\n"); return 1; } int port = lws_get_vhost_listen_port(vhost); -#else - int port = info.port; -#endif lwsl_notice(" Listening on port: %d\n", port); if (browser) { @@ -574,29 +575,20 @@ int main(int argc, char **argv) { open_uri(url); } -#if LWS_LIBRARY_VERSION_MAJOR >= 3 +#define sig_count 2 int sig_nums[] = {SIGINT, SIGTERM}; - int ns = sizeof(sig_nums) / sizeof(sig_nums[0]); - uv_signal_t signals[ns]; - for (int i = 0; i < ns; i++) { + uv_signal_t signals[sig_count]; + for (int i = 0; i < sig_count; i++) { uv_signal_init(server->loop, &signals[i]); uv_signal_start(&signals[i], signal_cb, sig_nums[i]); } lws_service(context, 0); - for (int i = 0; i < ns; i++) { + for (int i = 0; i < sig_count; i++) { uv_signal_stop(&signals[i]); } -#else -#if LWS_LIBRARY_VERSION_MAJOR < 2 - lws_uv_initloop(context, server->loop, signal_cb, 0); -#else - lws_uv_sigint_cfg(context, 1, signal_cb); - lws_uv_initloop(context, server->loop, 0); -#endif - lws_libuv_run(context, 0); -#endif +#undef sig_count lws_context_destroy(context); |