Buckets:
| volatile bool force_exit = false; | |
| 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); | |
| // 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}}; | |
| // websocket extensions | |
| static const struct lws_extension extensions[] = { | |
| {"permessage-deflate", lws_extension_callback_pm_deflate, "permessage-deflate"}, | |
| {"deflate-frame", lws_extension_callback_pm_deflate, "deflate_frame"}, | |
| {NULL, NULL, NULL}}; | |
| 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 = 5, | |
| .secs_since_valid_hangup = 10, | |
| .jitter_percent = 0, | |
| }; | |
| // command line options | |
| static const struct option options[] = {{"port", required_argument, NULL, 'p'}, | |
| {"interface", required_argument, NULL, 'i'}, | |
| {"socket-owner", required_argument, NULL, 'U'}, | |
| {"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'}, | |
| {"ping-interval", required_argument, NULL, 'P'}, | |
| {"srv-buf-size", required_argument, NULL, 'f'}, | |
| {"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'}, | |
| {"writable", no_argument, NULL, 'W'}, | |
| {"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'}, | |
| {"exit-no-conn", no_argument, NULL, 'q'}, | |
| {"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:U:c:H:u:g:s:w:I:b:P:f:6aSC:K:A:Wt:T:Om:oqBd:vh"; | |
| static void print_help() { | |
| // clang-format off | |
| fprintf(stderr, "ttyd is a tool for sharing terminal over the web\n\n" | |
| "USAGE:\n" | |
| " ttyd [options] <command> [<arguments...>]\n\n" | |
| "VERSION:\n" | |
| " %s\n\n" | |
| "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" | |
| " -U, --socket-owner User owner of the UNIX domain socket file, when enabled (eg: user:group)\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" | |
| " -W, --writable Allow clients to write to the TTY (readonly by default)\n" | |
| " -t, --client-option Send option to client (format: key=value), repeat to add more options\n" | |
| " -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" | |
| " -o, --once Accept only one client and exit on disconnection\n" | |
| " -q, --exit-no-conn Exit on all clients 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, max length: 128)\n" | |
| " -P, --ping-interval Websocket ping interval(sec) (default: 5)\n" | |
| " -f, --srv-buf-size Maximum chunk of file (in bytes) that can be sent at once, a larger value may improve throughput (default: 4096)\n" | |
| " -6, --ipv6 Enable IPv6 support\n" | |
| " -S, --ssl Enable SSL\n" | |
| " -C, --ssl-cert SSL certificate file path\n" | |
| " -K, --ssl-key SSL key file path\n" | |
| " -A, --ssl-ca SSL CA file path for client certificate verification\n" | |
| " -d, --debug Set log level (default: 7)\n" | |
| " -v, --version Print the version and exit\n" | |
| " -h, --help Print this text and exit\n\n" | |
| "Visit https://github.com/tsl0922/ttyd to get more information and report bugs.\n", | |
| TTYD_VERSION | |
| ); | |
| // 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->max_clients > 0) lwsl_notice(" max clients: %d\n", server->max_clients); | |
| if (server->once) lwsl_notice(" once: true\n"); | |
| if (server->exit_no_conn) lwsl_notice(" exit_no_conn: 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_warn("The --writable option is not set, will start in readonly mode\n"); | |
| } | |
| static struct server *server_new(int argc, char **argv, int start) { | |
| struct server *ts; | |
| size_t cmd_len = 0; | |
| ts = xmalloc(sizeof(struct server)); | |
| memset(ts, 0, sizeof(struct server)); | |
| ts->client_count = 0; | |
| ts->sig_code = SIGHUP; | |
| snprintf(ts->terminal_type, sizeof(ts->terminal_type), "%s", "xterm-256color"); | |
| get_sig_name(ts->sig_code, ts->sig_name, sizeof(ts->sig_name)); | |
| if (start == argc) return ts; | |
| int cmd_argc = argc - start; | |
| char **cmd_argv = &argv[start]; | |
| ts->argv = xmalloc(sizeof(char *) * (cmd_argc + 1)); | |
| for (int i = 0; i < cmd_argc; i++) { | |
| ts->argv[i] = strdup(cmd_argv[i]); | |
| cmd_len += strlen(ts->argv[i]); | |
| if (i != cmd_argc - 1) { | |
| cmd_len++; // for space | |
| } | |
| } | |
| ts->argv[cmd_argc] = NULL; | |
| ts->argc = cmd_argc; | |
| ts->command = xmalloc(cmd_len + 1); | |
| char *ptr = ts->command; | |
| for (int i = 0; i < cmd_argc; i++) { | |
| size_t len = strlen(ts->argv[i]); | |
| ptr = (char *)memcpy(ptr, ts->argv[i], len + 1) + len; | |
| if (i != cmd_argc - 1) { | |
| *ptr++ = ' '; | |
| } | |
| } | |
| *ptr = '\0'; // null terminator | |
| ts->loop = xmalloc(sizeof *ts->loop); | |
| uv_loop_init(ts->loop); | |
| return ts; | |
| } | |
| 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); | |
| 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_loop_close(ts->loop); | |
| free(ts->loop); | |
| free(ts); | |
| } | |
| static void signal_cb(uv_signal_t *watcher, int signum) { | |
| char sig_name[20]; | |
| switch (watcher->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); | |
| break; | |
| default: | |
| signal(SIGABRT, SIG_DFL); | |
| abort(); | |
| } | |
| if (force_exit) exit(EXIT_FAILURE); | |
| force_exit = true; | |
| lws_cancel_service(context); | |
| uv_stop(server->loop); | |
| lwsl_notice("send ^C to force exit.\n"); | |
| } | |
| 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) { | |
| // make a copy of argc and argv | |
| int argc_copy = argc; | |
| char **argv_copy = xmalloc(sizeof(char *) * argc); | |
| for (int i = 0; i < argc; i++) { | |
| argv_copy[i] = strdup(argv[i]); | |
| } | |
| // do not print error message for invalid option | |
| opterr = 0; | |
| while (getopt_long(argc_copy, argv_copy, opt_string, options, NULL) != -1) | |
| ; | |
| int start = argc; | |
| if (optind < argc) { | |
| char *command = argv_copy[optind]; | |
| for (int i = 0; i < argc; i++) { | |
| if (strcmp(argv[i], command) == 0) { | |
| start = i; | |
| break; | |
| } | |
| } | |
| } | |
| // free argv copy | |
| for (int i = 0; i < argc; i++) { | |
| free(argv_copy[i]); | |
| } | |
| free(argv_copy); | |
| // reset for next use | |
| opterr = 1; | |
| optind = 0; | |
| return start; | |
| } | |
| int main(int argc, char **argv) { | |
| if (argc == 1) { | |
| print_help(); | |
| return 0; | |
| } | |
| if (!conpty_init()) { | |
| fprintf(stderr, "ERROR: ConPTY init failed! Make sure you are on Windows 10 1809 or later."); | |
| return 1; | |
| } | |
| int start = calc_command_start(argc, argv); | |
| server = server_new(argc, argv, start); | |
| struct lws_context_creation_info info; | |
| memset(&info, 0, sizeof(info)); | |
| info.port = 7681; | |
| info.iface = NULL; | |
| info.protocols = protocols; | |
| 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.extensions = extensions; | |
| info.max_http_header_data = 65535; | |
| int debug_level = LLL_ERR | LLL_WARN | LLL_NOTICE; | |
| char iface[128] = ""; | |
| char socket_owner[128] = ""; | |
| bool browser = false; | |
| bool ssl = false; | |
| char cert_path[1024] = ""; | |
| char key_path[1024] = ""; | |
| char ca_path[1024] = ""; | |
| struct json_object *client_prefs = json_object_new_object(); | |
| json_object_object_add(client_prefs, "isWindows", json_object_new_boolean(true)); | |
| // parse command line options | |
| int c; | |
| while ((c = getopt_long(start, argv, opt_string, options, NULL)) != -1) { | |
| switch (c) { | |
| case 'h': | |
| print_help(); | |
| return 0; | |
| case 'v': | |
| printf("ttyd version %s\n", TTYD_VERSION); | |
| return 0; | |
| case 'd': | |
| debug_level = parse_int("debug", optarg); | |
| break; | |
| case 'a': | |
| server->url_arg = true; | |
| break; | |
| case 'W': | |
| server->writable = true; | |
| break; | |
| case 'O': | |
| server->check_origin = true; | |
| break; | |
| case 'm': | |
| server->max_clients = parse_int("max-clients", optarg); | |
| break; | |
| case 'o': | |
| server->once = true; | |
| break; | |
| case 'q': | |
| server->exit_no_conn = true; | |
| break; | |
| case 'B': | |
| browser = true; | |
| break; | |
| case 'p': | |
| info.port = parse_int("port", optarg); | |
| if (info.port < 0) { | |
| fprintf(stderr, "ttyd: invalid port: %s\n", optarg); | |
| return -1; | |
| } | |
| break; | |
| case 'i': | |
| strncpy(iface, optarg, sizeof(iface) - 1); | |
| iface[sizeof(iface) - 1] = '\0'; | |
| break; | |
| case 'U': | |
| strncpy(socket_owner, optarg, sizeof(socket_owner) - 1); | |
| socket_owner[sizeof(socket_owner) - 1] = '\0'; | |
| break; | |
| case 'c': | |
| if (strchr(optarg, ':') == NULL) { | |
| fprintf(stderr, "ttyd: invalid credential, format: username:password\n"); | |
| return -1; | |
| } | |
| 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 = parse_int("uid", optarg); | |
| break; | |
| case 'g': | |
| info.gid = parse_int("gid", optarg); | |
| break; | |
| case 's': { | |
| int sig = get_sig(optarg); | |
| if (sig > 0) { | |
| server->sig_code = sig; | |
| get_sig_name(sig, server->sig_name, sizeof(server->sig_name)); | |
| } else { | |
| fprintf(stderr, "ttyd: invalid signal: %s\n", optarg); | |
| return -1; | |
| } | |
| } break; | |
| case 'w': | |
| server->cwd = strdup(optarg); | |
| break; | |
| case 'I': | |
| if (!strncmp(optarg, "~/", 2)) { | |
| const char *home = getenv("HOME"); | |
| size_t index_len = strlen(home) + strlen(optarg); | |
| server->index = malloc(index_len); | |
| snprintf(server->index, index_len, "%s%s", home, optarg + 1); | |
| } else { | |
| server->index = strdup(optarg); | |
| } | |
| struct stat st; | |
| if (stat(server->index, &st) == -1) { | |
| 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); | |
| return -1; | |
| } | |
| break; | |
| case 'b': { | |
| char path[128]; | |
| strncpy(path, optarg, 128); | |
| size_t len = strlen(path); | |
| while (len && path[len - 1] == '/') path[--len] = 0; // trim trailing / | |
| if (!len) break; | |
| sc(ws) sc(index) sc(token) sc(parent) | |
| } break; | |
| 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 = interval; | |
| retry.secs_since_valid_hangup = interval + 7; | |
| } break; | |
| case 'f': { | |
| int serv_buf_size = parse_int("srv-buf-size", optarg); | |
| if (serv_buf_size < 0) { | |
| fprintf(stderr, "ttyd: invalid srv-buf-size: %s\n", optarg); | |
| return -1; | |
| } | |
| info.pt_serv_buf_size = serv_buf_size; | |
| } break; | |
| case '6': | |
| info.options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6); | |
| break; | |
| case 'S': | |
| ssl = true; | |
| break; | |
| case 'C': | |
| strncpy(cert_path, optarg, sizeof(cert_path) - 1); | |
| cert_path[sizeof(cert_path) - 1] = '\0'; | |
| break; | |
| case 'K': | |
| strncpy(key_path, optarg, sizeof(key_path) - 1); | |
| key_path[sizeof(key_path) - 1] = '\0'; | |
| break; | |
| case 'A': | |
| strncpy(ca_path, optarg, sizeof(ca_path) - 1); | |
| ca_path[sizeof(ca_path) - 1] = '\0'; | |
| break; | |
| case 'T': | |
| strncpy(server->terminal_type, optarg, sizeof(server->terminal_type) - 1); | |
| server->terminal_type[sizeof(server->terminal_type) - 1] = '\0'; | |
| break; | |
| case '?': | |
| break; | |
| case 't': | |
| optind--; | |
| for (; optind < start && *argv[optind] != '-'; optind++) { | |
| char *option = optarg; | |
| char *key = strsep(&option, "="); | |
| if (key == NULL) { | |
| 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); | |
| return -1; | |
| } | |
| struct json_object *obj = json_tokener_parse(value); | |
| json_object_object_add(client_prefs, key, obj != NULL ? obj : json_object_new_string(value)); | |
| } | |
| break; | |
| default: | |
| print_help(); | |
| return -1; | |
| } | |
| } | |
| server->prefs_json = strdup(json_object_to_json_string(client_prefs)); | |
| json_object_put(client_prefs); | |
| if (server->command == NULL || strlen(server->command) == 0) { | |
| fprintf(stderr, "ttyd: missing start command\n"); | |
| return -1; | |
| } | |
| lws_set_log_level(debug_level, NULL); | |
| char server_hdr[128] = ""; | |
| snprintf(server_hdr, sizeof(server_hdr), "ttyd/%s (libwebsockets/%s)", TTYD_VERSION, LWS_LIBRARY_VERSION); | |
| info.server_string = server_hdr; | |
| info.ws_ping_pong_interval = 5; | |
| info.retry_and_idle_policy = &retry; | |
| if (strlen(iface) > 0) { | |
| info.iface = iface; | |
| if (endswith(info.iface, ".sock") || endswith(info.iface, ".socket")) { | |
| info.options |= LWS_SERVER_OPTION_UNIX_SOCK; | |
| info.port = 0; // warmcat/libwebsockets#1985 | |
| strncpy(server->socket_path, info.iface, sizeof(server->socket_path) - 1); | |
| if (strlen(socket_owner) > 0) { | |
| info.unix_socket_perms = socket_owner; | |
| } | |
| fprintf(stderr, "libwebsockets is not compiled with UNIX domain socket support"); | |
| return -1; | |
| } | |
| } | |
| if (ssl) { | |
| info.ssl_cert_filepath = cert_path; | |
| info.ssl_private_key_filepath = key_path; | |
| info.ssl_options_set = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1; | |
| if (strlen(ca_path) > 0) { | |
| info.ssl_ca_filepath = ca_path; | |
| info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT; | |
| } | |
| info.options |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT | LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS; | |
| } | |
| 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); | |
| } | |
| void *foreign_loops[1]; | |
| foreign_loops[0] = server->loop; | |
| info.foreign_loops = foreign_loops; | |
| info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS; | |
| context = lws_create_context(&info); | |
| if (context == NULL) { | |
| lwsl_err("libwebsockets context creation failed\n"); | |
| return 1; | |
| } | |
| 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); | |
| lwsl_notice(" Listening on port: %d\n", port); | |
| if (browser) { | |
| char url[30]; | |
| snprintf(url, sizeof(url), "%s://localhost:%d", ssl ? "https" : "http", port); | |
| open_uri(url); | |
| } | |
| int sig_nums[] = {SIGINT, SIGTERM}; | |
| 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 < sig_count; i++) { | |
| uv_signal_stop(&signals[i]); | |
| } | |
| lws_context_destroy(context); | |
| // cleanup | |
| server_free(server); | |
| return 0; | |
| } | |
Xet Storage Details
- Size:
- 22.5 kB
- Xet hash:
- 9db61b95e209f6ab263ff31fbc30fb23dc6416eec480fc436cc7b12907a726fd
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.