\-c, \-\-credential USER[:PASSWORD]
Credential for Basic Authentication (format: username:password)
+.PP
+\-H, \-\-auth\-header <name>
+ HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
+
.PP
\-u, \-\-uid <uid>
User id to run with
-c, --credential USER[:PASSWORD]
Credential for Basic Authentication (format: username:password)
+ -H, --auth-header <name>
+ HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
+
-u, --uid <uid>
User id to run with
-DLWS_WITH_LEJP=OFF \
-DLWS_WITH_LEJP_CONF=OFF \
-DLWS_WITH_LWSAC=OFF \
- -DLWS_WITH_CUSTOM_HEADERS=OFF \
-DLWS_WITH_SEQUENCER=OFF \
..
make -j"$(nproc)" install
static char *html_cache = NULL;
static size_t html_cache_len = 0;
-static int check_auth(struct lws *wsi, struct pss_http *pss) {
- if (server->credential == NULL) return AUTH_OK;
-
- char buf[256];
- 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;
- }
-
+static int send_unauthorized(struct lws *wsi, unsigned int code, enum lws_token_indexes header) {
unsigned char buffer[1024 + LWS_PRE], *p, *end;
p = buffer + LWS_PRE;
end = p + sizeof(buffer) - LWS_PRE;
- char *body = strdup("401 Unauthorized\n");
- size_t n = strlen(body);
-
- if (lws_add_http_header_status(wsi, HTTP_STATUS_UNAUTHORIZED, &p, end) ||
- lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
- (unsigned char *)"Basic realm=\"ttyd\"", 18, &p, end) ||
- lws_add_http_header_content_length(wsi, n, &p, end) ||
- lws_finalize_http_header(wsi, &p, end) ||
+ if (lws_add_http_header_status(wsi, code, &p, end) ||
+ lws_add_http_header_by_token(wsi, header, (unsigned char *)"Basic realm=\"ttyd\"", 18, &p, end) ||
+ lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
- return AUTH_ERROR;
+ return AUTH_FAIL;
- pss->buffer = pss->ptr = body;
- pss->len = n;
- lws_callback_on_writable(wsi);
+ return lws_http_transaction_completed(wsi) ? AUTH_FAIL : AUTH_ERROR;
+}
- return AUTH_FAIL;
+static int check_auth(struct lws *wsi, struct pss_http *pss) {
+ if (server->auth_header != NULL) {
+ if (lws_hdr_custom_length(wsi, server->auth_header, strlen(server->auth_header)) > 0) return AUTH_OK;
+ return send_unauthorized(wsi, HTTP_STATUS_PROXY_AUTH_REQUIRED, WSI_TOKEN_HTTP_PROXY_AUTHENTICATE);
+ }
+
+ if(server->credential != NULL) {
+ char buf[256];
+ 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;
+ }
+ return send_unauthorized(wsi, HTTP_STATUS_UNAUTHORIZED, WSI_TOKEN_HTTP_WWW_AUTHENTICATE);
+ }
+
+ return AUTH_OK;
}
static bool accept_gzip(struct lws *wsi) {
lwsl_notice("HTTP %s - %s\n", path, rip);
}
-int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
- size_t len) {
+int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
struct pss_http *pss = (struct pss_http *)user;
unsigned char buffer[4096 + LWS_PRE], *p, *end;
char buf[256];
size_t n = sprintf(buf, "{\"token\": \"%s\"}", credential);
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
- (unsigned char *)"application/json;charset=utf-8", 30, &p,
- end) ||
+ (unsigned char *)"application/json;charset=utf-8", 30, &p, end) ||
lws_add_http_header_content_length(wsi, (unsigned long)n, &p, end) ||
lws_finalize_http_header(wsi, &p, end) ||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
// redirects `/base-path` to `/base-path/`
if (strcmp(pss->path, endpoints.parent) == 0) {
if (lws_add_http_header_status(wsi, HTTP_STATUS_FOUND, &p, end) ||
- lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION,
- (unsigned char *)endpoints.index,
+ lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, (unsigned char *)endpoints.index,
(int)strlen(endpoints.index), &p, end) ||
- lws_add_http_header_content_length(wsi, 0, &p, end) ||
- lws_finalize_http_header(wsi, &p, end) ||
+ lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
return 1;
goto try_to_reuse;
char *output = (char *)index_html;
size_t output_len = index_html_len;
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
- lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
- (const unsigned char *)content_type, 9, &p, end))
+ lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (const unsigned char *)content_type, 9, &p,
+ end))
return 1;
#ifdef LWS_WITH_HTTP_STREAM_COMPRESSION
if (!uncompress_html(&output, &output_len)) return 1;
#else
if (accept_gzip(wsi)) {
- if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
- (unsigned char *)"gzip", 4, &p, end))
+ if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING, (unsigned char *)"gzip", 4, &p, end))
return 1;
} else {
if (!uncompress_html(&output, &output_len)) return 1;
free(message);
}
-int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
- size_t len) {
+static bool check_auth(struct lws *wsi) {
+ if (server->auth_header != NULL) {
+ return lws_hdr_custom_length(wsi, server->auth_header, strlen(server->auth_header)) > 0;
+ }
+
+ if (server->credential != NULL) {
+ char buf[256];
+ 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);
+ }
+
+ return true;
+}
+
+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];
size_t n = 0;
lwsl_warn("refuse to serve WS client due to the --max-clients option.\n");
return 1;
}
- if (server->credential != NULL) {
- n = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
- if (n < 7 || !strstr(buf, "Basic ") || strcmp(buf + 6, server->credential)) return 1;
- }
+ if (!check_auth(wsi)) return 1;
n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_GET_URI);
#if defined(LWS_ROLE_H2)
}
break;
case RESIZE_TERMINAL:
- json_object_put(parse_window_size(pss->buffer + 1, pss->len - 1, &pss->process->columns,
- &pss->process->rows));
+ json_object_put(
+ parse_window_size(pss->buffer + 1, pss->len - 1, &pss->process->columns, &pss->process->rows));
pty_resize(pss->process);
break;
case PAUSE:
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'},
{"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";
-#endif
+static const char *opt_string = "p:i:c:H:u:g:s:I:b:P:6aSC:K:A:Rt:T:Om:oBd:vh";
static void print_help() {
// clang-format off
"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"
// 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;
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);
free(ts->command);
free(ts->prefs_json);
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;
#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);
+ 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 (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);
void *foreign_loops[1];
foreign_loops[0] = server->loop;
+#include <libwebsockets.h>
#include <stdbool.h>
#include <uv.h>
int client_count; // client count
char *prefs_json; // client preferences
char *credential; // encoded basic auth credential
+ char *auth_header; // header name used for auth proxy
char *index; // custom index.html
char *command; // full command line
char **argv; // command with arguments
return p;
}
-char *uppercase(char *str) {
- int i = 0;
- do {
- str[i] = (char)toupper(str[i]);
- } while (str[i++] != '\0');
- return str;
+char *uppercase(char *s) {
+ while(*s) {
+ *s = (char)toupper((int)*s);
+ s++;
+ }
+ return s;
+}
+
+char *lowercase(char *s) {
+ while(*s) {
+ *s = (char)tolower((int)*s);
+ s++;
+ }
+ return s;
}
bool endswith(const char *str, const char *suffix) {
void *xrealloc(void *p, size_t size);
// Convert a string to upper case
-char *uppercase(char *str);
+char *uppercase(char *s);
+
+// Convert a string to lower case
+char *lowercase(char *s);
// Check whether str ends with suffix
bool endswith(const char *str, const char *suffix);