mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
ws: support basic auth
Until now basic auth only protected http actions in the protected mount. This extends the existing basic auth scheme to also be consulted for ws upgrades if a "basic-auth" pvo exists on the selected protocol for the vhost. The value of the pvo is the usual basic auth credentials file same as for the http case.
This commit is contained in:
parent
110f1ecf46
commit
a74a966fbf
6 changed files with 145 additions and 89 deletions
|
@ -418,7 +418,7 @@ Content-Type: header.
|
|||
7) A mount can be protected by HTTP Basic Auth. This only makes sense when using
|
||||
https, since otherwise the password can be sniffed.
|
||||
|
||||
You can add a `basic-auth` entry on a mount like this
|
||||
You can add a `basic-auth` entry on an http mount like this
|
||||
|
||||
```
|
||||
{
|
||||
|
@ -447,6 +447,12 @@ a mount.
|
|||
After successful authentication, `WSI_TOKEN_HTTP_AUTHORIZATION` contains the
|
||||
authenticated username.
|
||||
|
||||
In the case you want to also protect being able to connect to a ws protocol on
|
||||
a particular vhost by requiring the http part can authenticate using Basic
|
||||
Auth before the ws upgrade, this is also possible. In this case, the
|
||||
"basic-auth": and filepath to the credentials file is passed as a pvo in the
|
||||
"ws-protocols" section of the vhost definition.
|
||||
|
||||
@section lwswscc Requiring a Client Cert on a vhost
|
||||
|
||||
You can make a vhost insist to get a client certificate from the peer before
|
||||
|
|
|
@ -209,7 +209,7 @@ lws_protocol_vh_priv_get(struct lws_vhost *vhost,
|
|||
return vhost->protocol_vh_privs[n];
|
||||
}
|
||||
|
||||
static const struct lws_protocol_vhost_options *
|
||||
const struct lws_protocol_vhost_options *
|
||||
lws_vhost_protocol_options(struct lws_vhost *vh, const char *name)
|
||||
{
|
||||
const struct lws_protocol_vhost_options *pvo = vh->pvo;
|
||||
|
|
|
@ -1374,6 +1374,9 @@ int
|
|||
lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p,
|
||||
const char *reason);
|
||||
|
||||
const struct lws_protocol_vhost_options *
|
||||
lws_vhost_protocol_options(struct lws_vhost *vh, const char *name);
|
||||
|
||||
const struct lws_http_mount *
|
||||
lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len);
|
||||
|
||||
|
|
|
@ -267,6 +267,18 @@ enum lws_parse_urldecode_results {
|
|||
LPUR_EXCESSIVE,
|
||||
};
|
||||
|
||||
enum lws_check_basic_auth_results {
|
||||
LCBA_CONTINUE,
|
||||
LCBA_FAILED_AUTH,
|
||||
LCBA_END_TRANSACTION,
|
||||
};
|
||||
|
||||
enum lws_check_basic_auth_results
|
||||
lws_check_basic_auth(struct lws *wsi, const char *basic_auth_login_file);
|
||||
|
||||
int
|
||||
lws_unauthorised_basic_auth(struct lws *wsi);
|
||||
|
||||
int
|
||||
lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len);
|
||||
|
||||
|
|
|
@ -808,7 +808,7 @@ lws_find_string_in_file(const char *filename, const char *string, int stringlen)
|
|||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
int
|
||||
lws_unauthorised_basic_auth(struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
|
@ -912,6 +912,76 @@ lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len)
|
|||
return -1;
|
||||
}
|
||||
|
||||
enum lws_check_basic_auth_results
|
||||
lws_check_basic_auth(struct lws *wsi, const char *basic_auth_login_file)
|
||||
{
|
||||
char b64[160], plain[(sizeof(b64) * 3) / 4], *pcolon;
|
||||
int m, ml, fi;
|
||||
|
||||
if (!basic_auth_login_file)
|
||||
return LCBA_CONTINUE;
|
||||
|
||||
/* Did he send auth? */
|
||||
ml = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||
if (!ml)
|
||||
return LCBA_FAILED_AUTH;
|
||||
|
||||
/* Disallow fragmentation monkey business */
|
||||
|
||||
fi = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_AUTHORIZATION];
|
||||
if (wsi->http.ah->frags[fi].nfrag) {
|
||||
lwsl_err("fragmented basic auth header not allowed\n");
|
||||
return LCBA_FAILED_AUTH;
|
||||
}
|
||||
|
||||
m = lws_hdr_copy(wsi, b64, sizeof(b64),
|
||||
WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||
if (m < 7) {
|
||||
lwsl_err("b64 auth too long\n");
|
||||
return LCBA_END_TRANSACTION;
|
||||
}
|
||||
|
||||
b64[5] = '\0';
|
||||
if (strcasecmp(b64, "Basic")) {
|
||||
lwsl_err("auth missing basic: %s\n", b64);
|
||||
return LCBA_END_TRANSACTION;
|
||||
}
|
||||
|
||||
/* It'll be like Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l */
|
||||
|
||||
m = lws_b64_decode_string(b64 + 6, plain, sizeof(plain) - 1);
|
||||
if (m < 0) {
|
||||
lwsl_err("plain auth too long\n");
|
||||
return LCBA_END_TRANSACTION;
|
||||
}
|
||||
|
||||
plain[m] = '\0';
|
||||
pcolon = strchr(plain, ':');
|
||||
if (!pcolon) {
|
||||
lwsl_err("basic auth format broken\n");
|
||||
return LCBA_END_TRANSACTION;
|
||||
}
|
||||
if (!lws_find_string_in_file(basic_auth_login_file, plain, m)) {
|
||||
lwsl_err("basic auth lookup failed\n");
|
||||
return LCBA_FAILED_AUTH;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrite WSI_TOKEN_HTTP_AUTHORIZATION so it is just the
|
||||
* authorized username
|
||||
*/
|
||||
|
||||
*pcolon = '\0';
|
||||
wsi->http.ah->frags[fi].len = lws_ptr_diff(pcolon, plain);
|
||||
pcolon = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||
strncpy(pcolon, plain, ml - 1);
|
||||
pcolon[ml - 1] = '\0';
|
||||
lwsl_info("%s: basic auth accepted for %s\n", __func__,
|
||||
lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_AUTHORIZATION));
|
||||
|
||||
return LCBA_CONTINUE;
|
||||
}
|
||||
|
||||
static const char * const oprot[] = {
|
||||
"http://", "https://"
|
||||
};
|
||||
|
@ -1144,70 +1214,14 @@ lws_http_action(struct lws *wsi)
|
|||
|
||||
/* basic auth? */
|
||||
|
||||
if (hit->basic_auth_login_file) {
|
||||
char b64[160], plain[(sizeof(b64) * 3) / 4], *pcolon;
|
||||
int m, ml, fi;
|
||||
|
||||
/* Did he send auth? */
|
||||
ml = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||
if (!ml)
|
||||
return lws_unauthorised_basic_auth(wsi);
|
||||
|
||||
/* Disallow fragmentation monkey business */
|
||||
|
||||
fi = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_AUTHORIZATION];
|
||||
if (wsi->http.ah->frags[fi].nfrag) {
|
||||
lwsl_err("fragmented basic auth header not allowed\n");
|
||||
return lws_unauthorised_basic_auth(wsi);
|
||||
}
|
||||
|
||||
n = HTTP_STATUS_FORBIDDEN;
|
||||
|
||||
m = lws_hdr_copy(wsi, b64, sizeof(b64),
|
||||
WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||
if (m < 7) {
|
||||
lwsl_err("b64 auth too long\n");
|
||||
goto transaction_result_n;
|
||||
}
|
||||
|
||||
b64[5] = '\0';
|
||||
if (strcasecmp(b64, "Basic")) {
|
||||
lwsl_err("auth missing basic: %s\n", b64);
|
||||
goto transaction_result_n;
|
||||
}
|
||||
|
||||
/* It'll be like Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l */
|
||||
|
||||
m = lws_b64_decode_string(b64 + 6, plain, sizeof(plain) - 1);
|
||||
if (m < 0) {
|
||||
lwsl_err("plain auth too long\n");
|
||||
goto transaction_result_n;
|
||||
}
|
||||
|
||||
plain[m] = '\0';
|
||||
pcolon = strchr(plain, ':');
|
||||
if (!pcolon) {
|
||||
lwsl_err("basic auth format broken\n");
|
||||
return lws_unauthorised_basic_auth(wsi);
|
||||
}
|
||||
if (!lws_find_string_in_file(hit->basic_auth_login_file,
|
||||
plain, m)) {
|
||||
lwsl_err("basic auth lookup failed\n");
|
||||
return lws_unauthorised_basic_auth(wsi);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrite WSI_TOKEN_HTTP_AUTHORIZATION so it is just the
|
||||
* authorized username
|
||||
*/
|
||||
|
||||
*pcolon = '\0';
|
||||
wsi->http.ah->frags[fi].len = lws_ptr_diff(pcolon, plain);
|
||||
pcolon = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_AUTHORIZATION);
|
||||
strncpy(pcolon, plain, ml - 1);
|
||||
pcolon[ml - 1] = '\0';
|
||||
lwsl_info("%s: basic auth accepted for %s\n", __func__,
|
||||
lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_AUTHORIZATION));
|
||||
switch(lws_check_basic_auth(wsi, hit->basic_auth_login_file)) {
|
||||
case LCBA_CONTINUE:
|
||||
break;
|
||||
case LCBA_FAILED_AUTH:
|
||||
return lws_unauthorised_basic_auth(wsi);
|
||||
case LCBA_END_TRANSACTION:
|
||||
lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
|
||||
return lws_http_transaction_completed(wsi);
|
||||
}
|
||||
|
||||
#if defined(LWS_WITH_HTTP_PROXY)
|
||||
|
@ -1557,11 +1571,6 @@ bail_nuke_ah:
|
|||
lws_header_table_detach(wsi, 1);
|
||||
|
||||
return 1;
|
||||
|
||||
transaction_result_n:
|
||||
lws_return_http_status(wsi, n, NULL);
|
||||
|
||||
return lws_http_transaction_completed(wsi);
|
||||
}
|
||||
|
||||
int
|
||||
|
|
|
@ -252,7 +252,9 @@ int
|
|||
lws_process_ws_upgrade(struct lws *wsi)
|
||||
{
|
||||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
const struct lws_protocol_vhost_options *pvos = NULL;
|
||||
const struct lws_protocols *pcol = NULL;
|
||||
const char *ws_prot_basic_auth = NULL;
|
||||
char buf[128], name[64];
|
||||
struct lws_tokenize ts;
|
||||
lws_tokenize_elem e;
|
||||
|
@ -260,23 +262,6 @@ lws_process_ws_upgrade(struct lws *wsi)
|
|||
if (!wsi->protocol)
|
||||
lwsl_err("NULL protocol at lws_read\n");
|
||||
|
||||
/*
|
||||
* We are upgrading to ws, so http/1.1 + h2 and keepalive + pipelined
|
||||
* header considerations about keeping the ah around no longer apply.
|
||||
*
|
||||
* However it's common for the first ws protocol data to have been
|
||||
* coalesced with the browser upgrade request and to already be in the
|
||||
* ah rx buffer.
|
||||
*/
|
||||
|
||||
lws_pt_lock(pt, __func__);
|
||||
|
||||
if (!wsi->h2_stream_carries_ws)
|
||||
lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED,
|
||||
&role_ops_ws);
|
||||
|
||||
lws_pt_unlock(pt);
|
||||
|
||||
/*
|
||||
* It's either websocket or h2->websocket
|
||||
*
|
||||
|
@ -389,6 +374,47 @@ check_protocol:
|
|||
|
||||
alloc_ws:
|
||||
|
||||
/*
|
||||
* Allow basic auth a look-in now we bound the wsi to the protocol.
|
||||
*
|
||||
* For vhost ws basic auth, it is "basic-auth": "path" as usual but
|
||||
* applied to the protocol's entry in the vhost's "ws-protocols":
|
||||
* section, as a pvo.
|
||||
*/
|
||||
|
||||
pvos = lws_vhost_protocol_options(wsi->vhost, wsi->protocol->name);
|
||||
if (pvos && pvos->options &&
|
||||
!lws_pvo_get_str((void *)pvos->options, "basic-auth",
|
||||
&ws_prot_basic_auth)) {
|
||||
lwsl_notice("%s: ws upgrade requires basic auth\n", __func__);
|
||||
switch(lws_check_basic_auth(wsi, ws_prot_basic_auth)) {
|
||||
case LCBA_CONTINUE:
|
||||
break;
|
||||
case LCBA_FAILED_AUTH:
|
||||
return lws_unauthorised_basic_auth(wsi);
|
||||
case LCBA_END_TRANSACTION:
|
||||
lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
|
||||
return lws_http_transaction_completed(wsi);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We are upgrading to ws, so http/1.1 + h2 and keepalive + pipelined
|
||||
* header considerations about keeping the ah around no longer apply.
|
||||
*
|
||||
* However it's common for the first ws protocol data to have been
|
||||
* coalesced with the browser upgrade request and to already be in the
|
||||
* ah rx buffer.
|
||||
*/
|
||||
|
||||
lws_pt_lock(pt, __func__);
|
||||
|
||||
if (!wsi->h2_stream_carries_ws)
|
||||
lws_role_transition(wsi, LWSIFR_SERVER, LRS_ESTABLISHED,
|
||||
&role_ops_ws);
|
||||
|
||||
lws_pt_unlock(pt);
|
||||
|
||||
/* allocate the ws struct for the wsi */
|
||||
|
||||
wsi->ws = lws_zalloc(sizeof(*wsi->ws), "ws struct");
|
||||
|
|
Loading…
Add table
Reference in a new issue