Skip to content

Commit e402edf

Browse files
committed
server: add auth proxy support
1 parent b2cda1d commit e402edf

File tree

9 files changed

+113
-75
lines changed

9 files changed

+113
-75
lines changed

man/ttyd.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
4646
\-c, \-\-credential USER[:PASSWORD]
4747
Credential for Basic Authentication (format: username:password)
4848

49+
.PP
50+
\-H, \-\-auth\-header <name>
51+
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
52+
4953
.PP
5054
\-u, \-\-uid <uid>
5155
User id to run with

man/ttyd.man.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ ttyd 1 "September 2016" ttyd "User Manual"
2828
-c, --credential USER[:PASSWORD]
2929
Credential for Basic Authentication (format: username:password)
3030

31+
-H, --auth-header <name>
32+
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
33+
3134
-u, --uid <uid>
3235
User id to run with
3336

scripts/cross-build.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ build_libwebsockets() {
106106
-DLWS_WITH_LEJP=OFF \
107107
-DLWS_WITH_LEJP_CONF=OFF \
108108
-DLWS_WITH_LWSAC=OFF \
109-
-DLWS_WITH_CUSTOM_HEADERS=OFF \
110109
-DLWS_WITH_SEQUENCER=OFF \
111110
..
112111
make -j"$(nproc)" install

src/http.c

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,36 @@ enum { AUTH_OK, AUTH_FAIL, AUTH_ERROR };
1111
static char *html_cache = NULL;
1212
static size_t html_cache_len = 0;
1313

14-
static int check_auth(struct lws *wsi, struct pss_http *pss) {
15-
if (server->credential == NULL) return AUTH_OK;
16-
17-
char buf[256];
18-
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
19-
if (len >= 7 && strstr(buf, "Basic ")) {
20-
if (!strcmp(buf + 6, server->credential)) return AUTH_OK;
21-
}
22-
14+
static int send_unauthorized(struct lws *wsi, unsigned int code, enum lws_token_indexes header) {
2315
unsigned char buffer[1024 + LWS_PRE], *p, *end;
2416
p = buffer + LWS_PRE;
2517
end = p + sizeof(buffer) - LWS_PRE;
2618

27-
char *body = strdup("401 Unauthorized\n");
28-
size_t n = strlen(body);
29-
30-
if (lws_add_http_header_status(wsi, HTTP_STATUS_UNAUTHORIZED, &p, end) ||
31-
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_WWW_AUTHENTICATE,
32-
(unsigned char *)"Basic realm=\"ttyd\"", 18, &p, end) ||
33-
lws_add_http_header_content_length(wsi, n, &p, end) ||
34-
lws_finalize_http_header(wsi, &p, end) ||
19+
if (lws_add_http_header_status(wsi, code, &p, end) ||
20+
lws_add_http_header_by_token(wsi, header, (unsigned char *)"Basic realm=\"ttyd\"", 18, &p, end) ||
21+
lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
3522
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
36-
return AUTH_ERROR;
23+
return AUTH_FAIL;
3724

38-
pss->buffer = pss->ptr = body;
39-
pss->len = n;
40-
lws_callback_on_writable(wsi);
25+
return lws_http_transaction_completed(wsi) ? AUTH_FAIL : AUTH_ERROR;
26+
}
4127

42-
return AUTH_FAIL;
28+
static int check_auth(struct lws *wsi, struct pss_http *pss) {
29+
if (server->auth_header != NULL) {
30+
if (lws_hdr_custom_length(wsi, server->auth_header, strlen(server->auth_header)) > 0) return AUTH_OK;
31+
return send_unauthorized(wsi, HTTP_STATUS_PROXY_AUTH_REQUIRED, WSI_TOKEN_HTTP_PROXY_AUTHENTICATE);
32+
}
33+
34+
if(server->credential != NULL) {
35+
char buf[256];
36+
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
37+
if (len >= 7 && strstr(buf, "Basic ")) {
38+
if (!strcmp(buf + 6, server->credential)) return AUTH_OK;
39+
}
40+
return send_unauthorized(wsi, HTTP_STATUS_UNAUTHORIZED, WSI_TOKEN_HTTP_WWW_AUTHENTICATE);
41+
}
42+
43+
return AUTH_OK;
4344
}
4445

4546
static bool accept_gzip(struct lws *wsi) {
@@ -89,8 +90,7 @@ static void access_log(struct lws *wsi, const char *path) {
8990
lwsl_notice("HTTP %s - %s\n", path, rip);
9091
}
9192

92-
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
93-
size_t len) {
93+
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
9494
struct pss_http *pss = (struct pss_http *)user;
9595
unsigned char buffer[4096 + LWS_PRE], *p, *end;
9696
char buf[256];
@@ -118,8 +118,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
118118
size_t n = sprintf(buf, "{\"token\": \"%s\"}", credential);
119119
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
120120
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
121-
(unsigned char *)"application/json;charset=utf-8", 30, &p,
122-
end) ||
121+
(unsigned char *)"application/json;charset=utf-8", 30, &p, end) ||
123122
lws_add_http_header_content_length(wsi, (unsigned long)n, &p, end) ||
124123
lws_finalize_http_header(wsi, &p, end) ||
125124
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
@@ -134,11 +133,9 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
134133
// redirects `/base-path` to `/base-path/`
135134
if (strcmp(pss->path, endpoints.parent) == 0) {
136135
if (lws_add_http_header_status(wsi, HTTP_STATUS_FOUND, &p, end) ||
137-
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION,
138-
(unsigned char *)endpoints.index,
136+
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, (unsigned char *)endpoints.index,
139137
(int)strlen(endpoints.index), &p, end) ||
140-
lws_add_http_header_content_length(wsi, 0, &p, end) ||
141-
lws_finalize_http_header(wsi, &p, end) ||
138+
lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
142139
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
143140
return 1;
144141
goto try_to_reuse;
@@ -157,15 +154,14 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
157154
char *output = (char *)index_html;
158155
size_t output_len = index_html_len;
159156
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
160-
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
161-
(const unsigned char *)content_type, 9, &p, end))
157+
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (const unsigned char *)content_type, 9, &p,
158+
end))
162159
return 1;
163160
#ifdef LWS_WITH_HTTP_STREAM_COMPRESSION
164161
if (!uncompress_html(&output, &output_len)) return 1;
165162
#else
166163
if (accept_gzip(wsi)) {
167-
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
168-
(unsigned char *)"gzip", 4, &p, end))
164+
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING, (unsigned char *)"gzip", 4, &p, end))
169165
return 1;
170166
} else {
171167
if (!uncompress_html(&output, &output_len)) return 1;

src/protocol.c

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,21 @@ static void wsi_output(struct lws *wsi, pty_buf_t *buf) {
137137
free(message);
138138
}
139139

140-
int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in,
141-
size_t len) {
140+
static bool check_auth(struct lws *wsi) {
141+
if (server->auth_header != NULL) {
142+
return lws_hdr_custom_length(wsi, server->auth_header, strlen(server->auth_header)) > 0;
143+
}
144+
145+
if (server->credential != NULL) {
146+
char buf[256];
147+
size_t n = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
148+
return n >= 7 && strstr(buf, "Basic ") && !strcmp(buf + 6, server->credential);
149+
}
150+
151+
return true;
152+
}
153+
154+
int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
142155
struct pss_tty *pss = (struct pss_tty *)user;
143156
char buf[256];
144157
size_t n = 0;
@@ -153,10 +166,7 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
153166
lwsl_warn("refuse to serve WS client due to the --max-clients option.\n");
154167
return 1;
155168
}
156-
if (server->credential != NULL) {
157-
n = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
158-
if (n < 7 || !strstr(buf, "Basic ") || strcmp(buf + 6, server->credential)) return 1;
159-
}
169+
if (!check_auth(wsi)) return 1;
160170

161171
n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_GET_URI);
162172
#if defined(LWS_ROLE_H2)
@@ -261,8 +271,8 @@ int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user,
261271
}
262272
break;
263273
case RESIZE_TERMINAL:
264-
json_object_put(parse_window_size(pss->buffer + 1, pss->len - 1, &pss->process->columns,
265-
&pss->process->rows));
274+
json_object_put(
275+
parse_window_size(pss->buffer + 1, pss->len - 1, &pss->process->columns, &pss->process->rows));
266276
pty_resize(pss->process);
267277
break;
268278
case PAUSE:

src/server.c

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static lws_retry_bo_t retry = {
5454
static const struct option options[] = {{"port", required_argument, NULL, 'p'},
5555
{"interface", required_argument, NULL, 'i'},
5656
{"credential", required_argument, NULL, 'c'},
57+
{"auth-header", required_argument, NULL, 'H'},
5758
{"uid", required_argument, NULL, 'u'},
5859
{"gid", required_argument, NULL, 'g'},
5960
{"signal", required_argument, NULL, 's'},
@@ -79,13 +80,7 @@ static const struct option options[] = {{"port", required_argument, NULL, 'p'},
7980
{"version", no_argument, NULL, 'v'},
8081
{"help", no_argument, NULL, 'h'},
8182
{NULL, 0, 0, 0}};
82-
83-
#if LWS_LIBRARY_VERSION_NUMBER < 4000000
84-
static const char *opt_string = "p:i:c:u:g:s:I:b:6aSC:K:A:Rt:T:Om:oBd:vh";
85-
#endif
86-
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
87-
static const char *opt_string = "p:i:c:u:g:s:I:b:P:6aSC:K:A:Rt:T:Om:oBd:vh";
88-
#endif
83+
static const char *opt_string = "p:i:c:H:u:g:s:I:b:P:6aSC:K:A:Rt:T:Om:oBd:vh";
8984

9085
static void print_help() {
9186
// clang-format off
@@ -97,7 +92,8 @@ static void print_help() {
9792
"OPTIONS:\n"
9893
" -p, --port Port to listen (default: 7681, use `0` for random port)\n"
9994
" -i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)\n"
100-
" -c, --credential Credential for Basic Authentication (format: username:password)\n"
95+
" -c, --credential Credential for basic authentication (format: username:password)\n"
96+
" -H, --auth-header HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication\n"
10197
" -u, --uid User id to run with\n"
10298
" -g, --gid Group id to run with\n"
10399
" -s, --signal Signal to send to the command when exit it (default: 1, SIGHUP)\n"
@@ -132,6 +128,28 @@ static void print_help() {
132128
// clang-format on
133129
}
134130

131+
static void print_config() {
132+
lwsl_notice("tty configuration:\n");
133+
if (server->credential != NULL) lwsl_notice(" credential: %s\n", server->credential);
134+
lwsl_notice(" start command: %s\n", server->command);
135+
lwsl_notice(" close signal: %s (%d)\n", server->sig_name, server->sig_code);
136+
lwsl_notice(" terminal type: %s\n", server->terminal_type);
137+
if (endpoints.parent[0]) {
138+
lwsl_notice("endpoints:\n");
139+
lwsl_notice(" base-path: %s\n", endpoints.parent);
140+
lwsl_notice(" index : %s\n", endpoints.index);
141+
lwsl_notice(" token : %s\n", endpoints.token);
142+
lwsl_notice(" websocket: %s\n", endpoints.ws);
143+
}
144+
if (server->auth_header != NULL) lwsl_notice(" auth header: %s\n", server->auth_header);
145+
if (server->check_origin) lwsl_notice(" check origin: true\n");
146+
if (server->url_arg) lwsl_notice(" allow url arg: true\n");
147+
if (server->readonly) lwsl_notice(" readonly: true\n");
148+
if (server->max_clients > 0) lwsl_notice(" max clients: %d\n", server->max_clients);
149+
if (server->once) lwsl_notice(" once: true\n");
150+
if (server->index != NULL) lwsl_notice(" custom index.html: %s\n", server->index);
151+
}
152+
135153
static struct server *server_new(int argc, char **argv, int start) {
136154
struct server *ts;
137155
size_t cmd_len = 0;
@@ -178,6 +196,7 @@ static struct server *server_new(int argc, char **argv, int start) {
178196
static void server_free(struct server *ts) {
179197
if (ts == NULL) return;
180198
if (ts->credential != NULL) free(ts->credential);
199+
if (ts->auth_header != NULL) free(ts->auth_header);
181200
if (ts->index != NULL) free(ts->index);
182201
free(ts->command);
183202
free(ts->prefs_json);
@@ -362,6 +381,9 @@ int main(int argc, char **argv) {
362381
lws_b64_encode_string(optarg, strlen(optarg), b64_text, sizeof(b64_text));
363382
server->credential = strdup(b64_text);
364383
break;
384+
case 'H':
385+
server->auth_header = strdup(optarg);
386+
break;
365387
case 'u':
366388
info.uid = parse_int("uid", optarg);
367389
break;
@@ -514,24 +536,15 @@ int main(int argc, char **argv) {
514536
#endif
515537

516538
lwsl_notice("ttyd %s (libwebsockets %s)\n", TTYD_VERSION, LWS_LIBRARY_VERSION);
517-
lwsl_notice("tty configuration:\n");
518-
if (server->credential != NULL) lwsl_notice(" credential: %s\n", server->credential);
519-
lwsl_notice(" start command: %s\n", server->command);
520-
lwsl_notice(" close signal: %s (%d)\n", server->sig_name, server->sig_code);
521-
lwsl_notice(" terminal type: %s\n", server->terminal_type);
522-
if (endpoints.parent[0]) {
523-
lwsl_notice("endpoints:\n");
524-
lwsl_notice(" base-path: %s\n", endpoints.parent);
525-
lwsl_notice(" index : %s\n", endpoints.index);
526-
lwsl_notice(" token : %s\n", endpoints.token);
527-
lwsl_notice(" websocket: %s\n", endpoints.ws);
539+
print_config();
540+
541+
// lws custom header requires lower case name, and terminating :
542+
if (server->auth_header != NULL) {
543+
size_t auth_header_len = strlen(server->auth_header);
544+
server->auth_header = xrealloc(server->auth_header, auth_header_len + 2);
545+
strcat(server->auth_header + auth_header_len, ":");
546+
lowercase(server->auth_header);
528547
}
529-
if (server->check_origin) lwsl_notice(" check origin: true\n");
530-
if (server->url_arg) lwsl_notice(" allow url arg: true\n");
531-
if (server->readonly) lwsl_notice(" readonly: true\n");
532-
if (server->max_clients > 0) lwsl_notice(" max clients: %d\n", server->max_clients);
533-
if (server->once) lwsl_notice(" once: true\n");
534-
if (server->index != NULL) lwsl_notice(" custom index.html: %s\n", server->index);
535548

536549
void *foreign_loops[1];
537550
foreign_loops[0] = server->loop;

src/server.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <libwebsockets.h>
12
#include <stdbool.h>
23
#include <uv.h>
34

@@ -58,6 +59,7 @@ struct server {
5859
int client_count; // client count
5960
char *prefs_json; // client preferences
6061
char *credential; // encoded basic auth credential
62+
char *auth_header; // header name used for auth proxy
6163
char *index; // custom index.html
6264
char *command; // full command line
6365
char **argv; // command with arguments

src/utils.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,20 @@ void *xrealloc(void *p, size_t size) {
3737
return p;
3838
}
3939

40-
char *uppercase(char *str) {
41-
int i = 0;
42-
do {
43-
str[i] = (char)toupper(str[i]);
44-
} while (str[i++] != '\0');
45-
return str;
40+
char *uppercase(char *s) {
41+
while(*s) {
42+
*s = (char)toupper((int)*s);
43+
s++;
44+
}
45+
return s;
46+
}
47+
48+
char *lowercase(char *s) {
49+
while(*s) {
50+
*s = (char)tolower((int)*s);
51+
s++;
52+
}
53+
return s;
4654
}
4755

4856
bool endswith(const char *str, const char *suffix) {

src/utils.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ void *xmalloc(size_t size);
1414
void *xrealloc(void *p, size_t size);
1515

1616
// Convert a string to upper case
17-
char *uppercase(char *str);
17+
char *uppercase(char *s);
18+
19+
// Convert a string to lower case
20+
char *lowercase(char *s);
1821

1922
// Check whether str ends with suffix
2023
bool endswith(const char *str, const char *suffix);

0 commit comments

Comments
 (0)