diff --git a/README.lwsws.md b/README.lwsws.md index ff559b9e..4692a770 100644 --- a/README.lwsws.md +++ b/README.lwsws.md @@ -68,43 +68,67 @@ Listing multiple vhosts looks something like this ``` { - "vhosts": [{ - "name": "warmcat.com", - "port": "443", - "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key", - "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt", - "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer", - "mounts": [{ - "mountpoint": "/", - "origin": "file:///var/www/warmcat.com", - "default": "index.html" - }] - }, { - "name": "warmcat2.com", - "port": "443", - "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key", - "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt", - "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer", - "mounts": [{ - "mountpoint": "/", - "origin": "file:///var/www/warmcat2.com", - "default": "index.html" - }] - } -] + "vhosts": [ { + "name": "localhost", + "port": "443", + "host-ssl-key": "/etc/pki/tls/private/libwebsockets.org.key", + "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt", + "host-ssl-ca": "/etc/pki/tls/certs/libwebsockets.org.cer", + "mounts": [{ + "mountpoint": "/", + "origin": "file:///var/www/libwebsockets.org", + "default": "index.html" + }, { + "mountpoint": "/testserver", + "origin": "file:///usr/local/share/libwebsockets-test-server", + "default": "test.html" + }], + # which protocols are enabled for this vhost, and optional + # vhost-specific config options for the protocol + # + "ws-protocols": [{ + "warmcat,timezoom": { + "status": "ok" + } + }] + }, + { + "name": "localhost", + "port": "7681", + "host-ssl-key": "/etc/pki/tls/private/libwebsockets.org.key", + "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt", + "host-ssl-ca": "/etc/pki/tls/certs/libwebsockets.org.cer", + "mounts": [{ + "mountpoint": "/", + "origin": ">https://localhost" + }] + }, + { + "name": "localhost", + "port": "80", + "mounts": [{ + "mountpoint": "/", + "origin": ">https://localhost" + }] + } + + ] } ``` +That sets up three vhosts all called "localhost" on ports 443 and 7681 with SSL, and port 80 without SSL but with a forced redirect to https://localhost + + Vhost name and port ------------------- The vhost name field is used to match on incoming SNI or Host: header, so it must always be the host name used to reach the vhost externally. -Vhosts may have the same name and different ports, these will each create a + - Vhosts may have the same name and different ports, these will each create a listening socket on the appropriate port. -They may also have the same port and different name: these will be treated as + - Vhosts may also have the same port and different name: these will be treated as true vhosts on one listening socket and the active vhost decided at SSL negotiation time (via SNI) or if no SSL, then after the Host: header from the client has been parsed. @@ -148,3 +172,20 @@ To help that happen conveniently, there are some new apis dumb increment, mirror and status protocol plugins are provided as examples. +Protocols +--------- + +Vhosts by default have available the union of any initial protocols from context creation time, and +any protocols exposed by plugins. + +Vhosts can select which plugins they want to offer and give them per-vhost settings using this syntax + +``` + "ws-protocols": [{ + "warmcat,timezoom": { + "status": "ok" + } + }] + +``` + diff --git a/lib/context.c b/lib/context.c index 8a965189..790b0dd2 100644 --- a/lib/context.c +++ b/lib/context.c @@ -129,10 +129,26 @@ lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *pr return vhost->protocol_vh_privs[n]; } +static struct lws_protocol_vhost_options * +lws_vhost_protocol_options(struct lws_vhost *vh, const char *name) +{ + struct lws_protocol_vhost_options *pvo = vh->pvo; + + while (pvo) { + // lwsl_notice("%s: '%s' '%s'\n", __func__, pvo->name, name); + if (!strcmp(pvo->name, name)) + return pvo; + pvo = pvo->next; + } + + return NULL; +} + int lws_protocol_init(struct lws_context *context) { struct lws_vhost *vh = context->vhost_list; + struct lws_protocol_vhost_options *pvo; struct lws wsi; int n; @@ -147,6 +163,15 @@ lws_protocol_init(struct lws_context *context) for (n = 0; n < vh->count_protocols; n++) { wsi.protocol = &vh->protocols[n]; + pvo = lws_vhost_protocol_options(vh, + vh->protocols[n].name); + if (pvo) + /* + * linked list of options specific to + * vh + protocol + */ + pvo = pvo->options; + /* * inform all the protocols that they are doing their one-time * initialization if they want to. @@ -155,7 +180,7 @@ lws_protocol_init(struct lws_context *context) * protocol ptrs so lws_get_context(wsi) etc can work */ vh->protocols[n].callback(&wsi, - LWS_CALLBACK_PROTOCOL_INIT, NULL, NULL, 0); + LWS_CALLBACK_PROTOCOL_INIT, NULL, pvo, 0); } vh = vh->vhost_next; @@ -176,7 +201,7 @@ lws_create_vhost(struct lws_context *context, #ifdef LWS_WITH_PLUGINS struct lws_plugin *plugin = context->plugin_list; struct lws_protocols *lwsp; - int m; + int m, n; #endif char *p; @@ -194,6 +219,9 @@ lws_create_vhost(struct lws_context *context, info->protocols[vh->count_protocols].callback; vh->count_protocols++) ; + + vh->pvo = info->pvo; + #ifdef LWS_WITH_PLUGINS if (plugin) { /* @@ -209,12 +237,22 @@ lws_create_vhost(struct lws_context *context, m = vh->count_protocols; memcpy(lwsp, info->protocols, sizeof(struct lws_protocols) * m); + while (plugin) { - memcpy(&lwsp[m], plugin->caps.protocols, - sizeof(struct lws_protocols) * - plugin->caps.count_protocols); - m += plugin->caps.count_protocols; - vh->count_protocols += plugin->caps.count_protocols; + for (n = 0; n < plugin->caps.count_protocols; n++) { + /* + * for compatibility's sake, no pvo implies + * allow all protocols + */ + if (!info->pvo || lws_vhost_protocol_options(vh, + plugin->caps.protocols[n].name)) { + memcpy(&lwsp[m], + &plugin->caps.protocols[n], + sizeof(struct lws_protocols)); + m++; + vh->count_protocols++; + } + } plugin = plugin->list; } vh->protocols = lwsp; @@ -222,7 +260,6 @@ lws_create_vhost(struct lws_context *context, #endif vh->protocols = info->protocols; - vh->mount_list = mounts; lwsl_notice("Creating Vhost '%s' port %d, %d protocols\n", diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index de485b3e..36a1d40c 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -1339,6 +1339,12 @@ LWS_EXTERN int lws_set_extension_option(struct lws *wsi, const char *ext_name, const char *opt_name, const char *opt_val); +struct lws_protocol_vhost_options { + struct lws_protocol_vhost_options *next; + struct lws_protocol_vhost_options *options; + const char *name; + const char *value; +}; /** * struct lws_context_creation_info - parameters to create context with @@ -1451,6 +1457,7 @@ struct lws_context_creation_info { const char *ecdh_curve; /* VH */ const char *vhost_name; /* VH */ const char *plugins_dir; /* context */ + struct lws_protocol_vhost_options *pvo; /* VH */ /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 839eab54..4ab471fb 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -654,6 +654,7 @@ struct lws_vhost { const char *iface; const struct lws_protocols *protocols; void **protocol_vh_privs; + struct lws_protocol_vhost_options *pvo; #ifdef LWS_OPENSSL_SUPPORT SSL_CTX *ssl_ctx; SSL_CTX *ssl_client_ctx; diff --git a/lib/server.c b/lib/server.c index 5a3ec799..93c98ab7 100644 --- a/lib/server.c +++ b/lib/server.c @@ -239,13 +239,13 @@ lws_http_action(struct lws *wsi) enum http_connection_type connection_type; enum http_version request_version; char content_length_str[32]; - struct lws_http_mount *hm; + struct lws_http_mount *hm, *hit = NULL; unsigned int n, count = 0; char http_version_str[10]; char http_conn_str[20]; int http_version_len; char *uri_ptr = NULL; - int uri_len = 0; + int uri_len = 0, best = 0; static const unsigned char methods[] = { WSI_TOKEN_GET_URI, @@ -395,25 +395,33 @@ lws_http_action(struct lws *wsi) hm = wsi->vhost->mount_list; while (hm) { - char *s = uri_ptr + hm->mountpoint_len; - - if (s[0] == '\0') - s = (char *)hm->def; - - if (!s) - s = "index.html"; - + lwsl_err("a %p\n", hm); if (uri_len >= hm->mountpoint_len && !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len)) { - n = lws_http_serve(wsi, s, hm->origin); - break; + if (hm->mountpoint_len > best) { + best = hm->mountpoint_len; + hit = hm; + } } hm = hm->mount_next; } +lwsl_err("x\n"); + if (hit) { + char *s = uri_ptr + hit->mountpoint_len; + + if (s[0] == '\0') + s = (char *)hit->def; + + if (!s) + s = "index.html"; +lwsl_err("b\n"); + n = lws_http_serve(wsi, s, hit->origin); + } else { + lwsl_err("c\n"); - if (!hm) n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, wsi->user_space, uri_ptr, uri_len); + } if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); diff --git a/lwsws/conf.c b/lwsws/conf.c index 4c94c5df..57eafd78 100644 --- a/lwsws/conf.c +++ b/lwsws/conf.c @@ -47,7 +47,10 @@ static const char * const paths_vhosts[] = { "vhosts[].host-ssl-ca", "vhosts[].mounts[].mountpoint", "vhosts[].mounts[].origin", - "vhosts[].mounts[].default" + "vhosts[].mounts[].default", + "vhosts[].ws-protocols[].*.*", + "vhosts[].ws-protocols[].*", + "vhosts[].ws-protocols[]" }; enum lejp_vhost_paths { @@ -61,6 +64,9 @@ enum lejp_vhost_paths { LEJPVP_MOUNTPOINT, LEJPVP_ORIGIN, LEJPVP_DEFAULT, + LEJPVP_PROTOCOL_NAME_OPT, + LEJPVP_PROTOCOL_NAME, + LEJPVP_PROTOCOL, }; struct jpargs { @@ -71,8 +77,18 @@ struct jpargs { char *p, *end, valid; struct lws_http_mount *head, *last; char *mountpoint, *origin, *def; + struct lws_protocol_vhost_options *pvo; }; +static void * +lwsws_align(struct jpargs *a) +{ + if ((unsigned long)(a->p) & 15) + a->p += 16 - ((unsigned long)(a->p) & 15); + + return a->p; +} + static int arg_to_bool(const char *s) { static const char * const on[] = { "on", "yes", "true" }; @@ -128,10 +144,16 @@ static char lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) { struct jpargs *a = (struct jpargs *)ctx->user; + struct lws_protocol_vhost_options *pvo; struct lws_http_mount *m; int n; +// lwsl_notice(" %d: %s (%d)\n", reason, ctx->path, ctx->path_match); +// for (n = 0; n < ctx->wildcount; n++) +// lwsl_notice(" %d\n", ctx->wild[n]); + if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) { + /* set the defaults for this vhost */ a->valid = 1; a->head = NULL; a->last = NULL; @@ -156,6 +178,7 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) "!DHE-RSA-AES256-SHA256:" "!AES256-GCM-SHA384:" "!AES256-SHA256"; + a->info->pvo = NULL; } if (reason == LEJPCB_OBJECT_START && @@ -165,6 +188,25 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->def = NULL; } + /* this catches, eg, vhosts[].ws-protocols[].xxx-protocol */ + if (reason == LEJPCB_OBJECT_START && + ctx->path_match == LEJPVP_PROTOCOL_NAME + 1) { + a->pvo = lwsws_align(a); + a->p += sizeof(*a->pvo); + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + a->pvo->next = a->info->pvo; + a->info->pvo = a->pvo; + a->pvo->name = a->p; + lwsl_err("adding %s\n", a->p); + a->p += n; + a->pvo->value = a->p; + a->pvo->options = NULL; + a->p += snprintf(a->p, a->end - a->p, "%s", ctx->buf); + *(a->p)++ = '\0'; + } + if (reason == LEJPCB_OBJECT_END && (ctx->path_match == LEJPVP + 1 || !ctx->path[0]) && a->valid) { @@ -232,6 +274,27 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) case LEJPVP_DEFAULT: a->def = a->p; break; + + case LEJPVP_PROTOCOL_NAME_OPT: + /* this catches, eg, + * vhosts[].ws-protocols[].xxx-protocol.yyy-option + * ie, these are options attached to a protocol with { } + */ + pvo = lwsws_align(a); + a->p += sizeof(*a->pvo); + + n = lejp_get_wildcard(ctx, 1, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + pvo->next = a->pvo->options; + a->pvo->options = pvo; + pvo->name = a->p; + a->p += n; + pvo->value = a->p; + pvo->options = NULL; + a->p += snprintf(a->p, a->end - a->p, "%s", ctx->buf); + *(a->p)++ = '\0'; + break; + default: return 0; } diff --git a/lwsws/lejp.c b/lwsws/lejp.c index 9eff6152..20a07340 100644 --- a/lwsws/lejp.c +++ b/lwsws/lejp.c @@ -97,16 +97,56 @@ lejp_change_callback(struct lejp_ctx *ctx, static void lejp_check_path_match(struct lejp_ctx *ctx) { + const char *p, *q; int n; /* we only need to check if a match is not active */ for (n = 0; !ctx->path_match && n < ctx->count_paths; n++) { - if (strcmp(ctx->path, ctx->paths[n])) + ctx->wildcount = 0; + p = ctx->path; + q = ctx->paths[n]; + while (*p && *q) { + if (*q != '*') { + if (*p != *q) + break; + p++; + q++; + continue; + } + ctx->wild[ctx->wildcount++] = p - ctx->path; + q++; + while (*p && *p != '.') + p++; + } + if (*p || *q) continue; + ctx->path_match = n + 1; ctx->path_match_len = ctx->ppos; return; } + + if (!ctx->path_match) + ctx->wildcount = 0; +} + +int +lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len) +{ + int n; + + if (wildcard >= ctx->wildcount || !len) + return 0; + + n = ctx->wild[wildcard]; + + while (--len && n < ctx->ppos && ctx->path[n] != '.') + *dest++ = ctx->path[n++]; + + *dest = '\0'; + n++; + + return n - ctx->wild[wildcard]; } /** diff --git a/lwsws/lejp.h b/lwsws/lejp.h index 455c203e..5832e998 100644 --- a/lwsws/lejp.h +++ b/lwsws/lejp.h @@ -187,6 +187,7 @@ struct lejp_ctx { struct _lejp_stack st[LEJP_MAX_DEPTH]; unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */ + unsigned short wild[LEJP_MAX_INDEX_DEPTH]; /* index array */ char path[LEJP_MAX_PATH]; char buf[LEJP_STRING_CHUNK]; @@ -209,6 +210,7 @@ struct lejp_ctx { unsigned char count_paths; unsigned char path_match; unsigned char path_match_len; + unsigned char wildcount; }; extern void @@ -225,3 +227,6 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len); extern void lejp_change_callback(struct lejp_ctx *ctx, char (*callback)(struct lejp_ctx *ctx, char reason)); + +extern int +lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len); diff --git a/plugins/protocol_dumb_increment.c b/plugins/protocol_dumb_increment.c index 499073dd..f26e5936 100644 --- a/plugins/protocol_dumb_increment.c +++ b/plugins/protocol_dumb_increment.c @@ -58,6 +58,7 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: + lwsl_notice("%s: pvo %p\n", __func__, in); vhd = lws_protocol_vh_priv_zalloc(lws_vhost_get(wsi), lws_protocol_get(wsi), sizeof(struct per_vhost_data__dumb_increment));