Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ else()
set(CMAKE_C_STANDARD 99)
endif()

set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c)
set(SOURCE_FILES src/totp.c src/utils.c src/pty.c src/protocol.c src/http.c src/server.c)

include(FindPackageHandleStandardArgs)

Expand Down Expand Up @@ -80,6 +80,8 @@ else()
endif()
endif()

list(APPEND LINK_LIBS m)

add_executable(${PROJECT_NAME} ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ttyd is a simple command-line tool for sharing terminal over the web.
- SSL support based on [OpenSSL](https://www.openssl.org) / [Mbed TLS](https://github.com/Mbed-TLS/mbedtls)
- Run any custom command with options
- Basic authentication support and many other custom options
- Two-factor authentication (2FA) in which the TOTP code is appended to the password
- Cross platform: macOS, Linux, FreeBSD/OpenBSD, [OpenWrt](https://openwrt.org), Windows

> ❤ Special thanks to [JetBrains](https://www.jetbrains.com/?from=ttyd) for sponsoring the opensource license to this project.
Expand Down Expand Up @@ -69,6 +70,7 @@ OPTIONS:
-i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)
-U, --socket-owner User owner of the UNIX domain socket file, when enabled (eg: user:group)
-c, --credential Credential for basic authentication (format: username:password)
-1, --totp Time-based one-time password secret (format: [DIGEST:]SECRET[:DIGITS[:INTERVAL[:OFFSET]]])
-H, --auth-header HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
-u, --uid User id to run with
-g, --gid Group id to run with
Expand Down
4 changes: 4 additions & 0 deletions man/ttyd.1
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
-c, --credential USER[:PASSWORD]
Credential for Basic Authentication (format: username:password)

.PP
-1, --totp [DIGEST:]SECRET[:DIGITS[:INTERVAL[:OFFSET]]]
Time-based one-time password secret (format: [DIGEST:]SECRET[:DIGITS[:INTERVAL[:OFFSET]]])

.PP
-H, --auth-header
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
Expand Down
3 changes: 3 additions & 0 deletions man/ttyd.man.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ ttyd 1 "September 2016" ttyd "User Manual"
-c, --credential USER[:PASSWORD]
Credential for Basic Authentication (format: username:password)

-1, --totp [DIGEST:]SECRET[:DIGITS[:INTERVAL[:OFFSET]]]
Time-based one-time password secret (format: [DIGEST:]SECRET[:DIGITS[:INTERVAL[:OFFSET]]])

-H, --auth-header <name>
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication

Expand Down
21 changes: 20 additions & 1 deletion src/http.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include <libwebsockets.h>
#include <string.h>
#include <zlib.h>
#include <time.h>

#include "html.h"
#include "server.h"
#include "utils.h"
#include "totp.h"

enum { AUTH_OK, AUTH_FAIL, AUTH_ERROR };

Expand Down Expand Up @@ -33,9 +35,23 @@ static int check_auth(struct lws *wsi, struct pss_http *pss) {

if(server->credential != NULL) {
char buf[256];
char expect[256];
char b64expect[256];
char code[16];
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
if (len >= 7 && strstr(buf, "Basic ")) {
if (!strcmp(buf + 6, server->credential)) return AUTH_OK;
if(server->totp != NULL && TOTP_GenerateFromSpec(server->totp, time(NULL), code, sizeof(code)) == 0) {
if (server->last_totp_code[0] == '\0' || strcmp(code, server->last_totp_code) != 0) {
snprintf(expect, sizeof(expect), "%s%s", server->credential_dec, code);
lws_b64_encode_string(expect, strlen(expect), b64expect, sizeof(b64expect));
if (!strcmp(buf + 6, b64expect)) {
strncpy(server->last_totp_code, code, sizeof(server->last_totp_code));
return AUTH_OK;
}
}
} else {
if (!strcmp(buf + 6, server->credential)) return AUTH_OK;
}
}
return send_unauthorized(wsi, HTTP_STATUS_UNAUTHORIZED, WSI_TOKEN_HTTP_WWW_AUTHENTICATE);
}
Expand Down Expand Up @@ -102,11 +118,14 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
snprintf(pss->path, sizeof(pss->path), "%s", (const char *)in);
switch (check_auth(wsi, pss)) {
case AUTH_OK:
lwsl_notice("AUTH_OK!!!\n");
break;
case AUTH_FAIL:
lwsl_notice("AUTH_FAIL!!!\n");
return 0;
case AUTH_ERROR:
default:
lwsl_notice("AUTH_ERROR!!!\n");
return 1;
}

Expand Down
27 changes: 25 additions & 2 deletions src/protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "pty.h"
#include "server.h"
#include "utils.h"
#include "totp.h"

// initial message list
static char initial_cmds[] = {SET_WINDOW_TITLE, SET_PREFERENCES};
Expand Down Expand Up @@ -187,8 +189,24 @@ static bool check_auth(struct lws *wsi, struct pss_tty *pss) {

if (server->credential != NULL) {
char buf[256];
char expect[256];
char b64expect[256];
char code[16];
size_t n = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
return n >= 7 && strstr(buf, "Basic ") && !strcmp(buf + 6, server->credential);
if (n >= 7 && strstr(buf, "Basic ")) {
if(server->totp != NULL && TOTP_GenerateFromSpec(server->totp, time(NULL), code, sizeof(code)) == 0) {
snprintf(expect, sizeof(expect), "%s%s", server->credential_dec, server->last_totp_code);
lws_b64_encode_string(expect, strlen(expect), b64expect, sizeof(b64expect));
if (!strcmp(buf + 6, b64expect)) return true;
} else {
if (!strcmp(buf + 6, server->credential)) return true;
}
} else {
if(server->totp != NULL && TOTP_GenerateFromSpec(server->totp, time(NULL), code, sizeof(code)) == 0) {
if (!strcmp(code, server->last_totp_code)) return true;
}
}
return false;
}

return true;
Expand All @@ -209,7 +227,12 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
lwsl_warn("refuse to serve WS client due to the --max-clients option.\n");
return 1;
}
if (!check_auth(wsi, pss)) return 1;
if (!check_auth(wsi, pss)) {
lwsl_notice("AUTH_ERROR!!! (protocol.c)\n");
return 1;
} else {
lwsl_notice("AUTH_OK!!! (protocol.c)\n");
}

n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_GET_URI);
#if defined(LWS_ROLE_H2)
Expand Down
13 changes: 12 additions & 1 deletion src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ 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'},
{"totp", required_argument, NULL, '1'},
{"auth-header", required_argument, NULL, 'H'},
{"uid", required_argument, NULL, 'u'},
{"gid", required_argument, NULL, 'g'},
Expand Down Expand Up @@ -84,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:f:6aSC:K:A:Wt:T:Om:oqBd:vh";
static const char *opt_string = "p:i:U:c:1:H:u:g:s:w:I:b:P:f:6aSC:K:A:Wt:T:Om:oqBd:vh";

static void print_help() {
// clang-format off
Expand All @@ -98,6 +99,7 @@ static void print_help() {
" -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"
" -1, --totp Time-based one-time password secret (format: [DIGEST:]SECRET[:DIGITS[:INTERVAL[:OFFSET]]])\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"
Expand Down Expand Up @@ -139,6 +141,7 @@ static void print_help() {
static void print_config() {
lwsl_notice("tty configuration:\n");
if (server->credential != NULL) lwsl_notice(" credential: %s\n", server->credential);
if (server->totp != NULL) lwsl_notice(" totp secret: %s\n", server->totp);
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);
Expand Down Expand Up @@ -206,6 +209,8 @@ 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->credential_dec != NULL) free(ts->credential_dec);
if (ts->totp != NULL) free(ts->totp);
if (ts->auth_header != NULL) free(ts->auth_header);
if (ts->index != NULL) free(ts->index);
if (ts->cwd != NULL) free(ts->cwd);
Expand Down Expand Up @@ -344,6 +349,8 @@ int main(int argc, char **argv) {
json_object_object_add(client_prefs, "isWindows", json_object_new_boolean(true));
#endif

server->last_totp_code[0] = '\0';

// parse command line options
int c;
while ((c = getopt_long(start, argv, opt_string, options, NULL)) != -1) {
Expand Down Expand Up @@ -401,6 +408,10 @@ int main(int argc, char **argv) {
char b64_text[256];
lws_b64_encode_string(optarg, strlen(optarg), b64_text, sizeof(b64_text));
server->credential = strdup(b64_text);
server->credential_dec = strdup(optarg);
break;
case '1':
server->totp = strdup(optarg);
break;
case 'H':
server->auth_header = strdup(optarg);
Expand Down
3 changes: 3 additions & 0 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ struct server {
int client_count; // client count
char *prefs_json; // client preferences
char *credential; // encoded basic auth credential
char *credential_dec; // decoded basic auth credential
char *totp; // time-based one-time password secret
char last_totp_code[16]; // last-used TOTP code, max 8 digits + NUL
char *auth_header; // header name used for auth proxy
char *index; // custom index.html
char *command; // full command line
Expand Down
Loading