2 * libwebsockets web server application
4 * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation:
9 * version 2.1 of the License.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 #include "private-libwebsockets.h"
26 /* this is needed for Travis CI */
30 #define ESC_INSTALL_DATADIR "_lws_ddir_"
32 static const char * const paths_global[] = {
35 "global.count-threads",
37 "global.server-string",
39 "global.ws-pingpong-secs",
40 "global.timeout-secs",
41 "global.reject-service-keywords[].*",
42 "global.reject-service-keywords[]",
45 enum lejp_global_paths {
54 LWJPGP_REJECT_SERVICE_KEYWORDS_NAME,
55 LWJPGP_REJECT_SERVICE_KEYWORDS
58 static const char * const paths_vhosts[] = {
64 "vhosts[].unix-socket",
66 "vhosts[].host-ssl-key",
67 "vhosts[].host-ssl-cert",
68 "vhosts[].host-ssl-ca",
69 "vhosts[].access-log",
70 "vhosts[].mounts[].mountpoint",
71 "vhosts[].mounts[].origin",
72 "vhosts[].mounts[].protocol",
73 "vhosts[].mounts[].default",
74 "vhosts[].mounts[].auth-mask",
75 "vhosts[].mounts[].cgi-timeout",
76 "vhosts[].mounts[].cgi-env[].*",
77 "vhosts[].mounts[].cache-max-age",
78 "vhosts[].mounts[].cache-reuse",
79 "vhosts[].mounts[].cache-revalidate",
80 "vhosts[].mounts[].basic-auth",
81 "vhosts[].mounts[].cache-intermediaries",
82 "vhosts[].mounts[].extra-mimetypes.*",
83 "vhosts[].mounts[].interpret.*",
84 "vhosts[].ws-protocols[].*.*",
85 "vhosts[].ws-protocols[].*",
86 "vhosts[].ws-protocols[]",
87 "vhosts[].keepalive_timeout",
88 "vhosts[].enable-client-ssl",
90 "vhosts[].ecdh-curve",
93 "vhosts[].ssl-option-set",
94 "vhosts[].ssl-option-clear",
95 "vhosts[].mounts[].pmo[].*",
96 "vhosts[].headers[].*",
98 "vhosts[].client-ssl-key",
99 "vhosts[].client-ssl-cert",
100 "vhosts[].client-ssl-ca",
101 "vhosts[].client-ssl-ciphers",
104 enum lejp_vhost_paths {
113 LEJPVP_HOST_SSL_CERT,
118 LEJPVP_MOUNT_PROTOCOL,
120 LEJPVP_DEFAULT_AUTH_MASK,
123 LEJPVP_MOUNT_CACHE_MAX_AGE,
124 LEJPVP_MOUNT_CACHE_REUSE,
125 LEJPVP_MOUNT_CACHE_REVALIDATE,
126 LEJPVP_MOUNT_BASIC_AUTH,
127 LEJPVP_MOUNT_CACHE_INTERMEDIARIES,
128 LEJPVP_MOUNT_EXTRA_MIMETYPES,
129 LEJPVP_MOUNT_INTERPRET,
130 LEJPVP_PROTOCOL_NAME_OPT,
131 LEJPVP_PROTOCOL_NAME,
133 LEJPVP_KEEPALIVE_TIMEOUT,
134 LEJPVP_ENABLE_CLIENT_SSL,
139 LEJPVP_SSL_OPTION_SET,
140 LEJPVP_SSL_OPTION_CLEAR,
144 LEJPVP_CLIENT_SSL_KEY,
145 LEJPVP_CLIENT_SSL_CERT,
146 LEJPVP_CLIENT_SSL_CA,
147 LEJPVP_CLIENT_CIPHERS,
150 static const char * const parser_errs[] = {
154 "Expected closing '}'",
157 "Illegal unescaped control char",
158 "Illegal escape format",
159 "Illegal hex number",
161 "Illegal value start",
162 "Digit required after decimal point",
164 "Bad exponent format",
169 "JSON nesting limit exceeded",
170 "Nesting tracking used up",
172 "Comma or block end expected",
174 "Parser callback errored (see earlier error)",
177 #define MAX_PLUGIN_DIRS 10
180 struct lws_context_creation_info *info;
181 struct lws_context *context;
182 const struct lws_protocols *protocols;
183 const struct lws_extension *extensions;
184 char *p, *end, valid;
185 struct lws_http_mount *head, *last;
187 struct lws_protocol_vhost_options *pvo;
188 struct lws_protocol_vhost_options *pvo_em;
189 struct lws_protocol_vhost_options *pvo_int;
190 struct lws_http_mount m;
191 const char **plugin_dirs;
192 int count_plugin_dirs;
194 unsigned int enable_client_ssl:1;
195 unsigned int fresh_mount:1;
196 unsigned int any_vhosts:1;
200 lwsws_align(struct jpargs *a)
202 if ((lws_intptr_t)(a->p) & 15)
203 a->p += 16 - ((lws_intptr_t)(a->p) & 15);
209 arg_to_bool(const char *s)
211 static const char * const on[] = { "on", "yes", "true" };
217 for (n = 0; n < ARRAY_SIZE(on); n++)
218 if (!strcasecmp(s, on[n]))
225 lejp_globals_cb(struct lejp_ctx *ctx, char reason)
227 struct jpargs *a = (struct jpargs *)ctx->user;
228 struct lws_protocol_vhost_options *rej;
231 /* we only match on the prepared path strings */
232 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
235 /* this catches, eg, vhosts[].headers[].xxx */
236 if (reason == LEJPCB_VAL_STR_END &&
237 ctx->path_match == LWJPGP_REJECT_SERVICE_KEYWORDS_NAME + 1) {
238 rej = lwsws_align(a);
239 a->p += sizeof(*rej);
241 n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
242 rej->next = a->info->reject_service_keywords;
243 a->info->reject_service_keywords = rej;
245 lwsl_notice(" adding rej %s=%s\n", a->p, ctx->buf);
253 switch (ctx->path_match - 1) {
255 a->info->uid = atoi(ctx->buf);
258 a->info->gid = atoi(ctx->buf);
260 case LEJPGP_COUNT_THREADS:
261 a->info->count_threads = atoi(ctx->buf);
263 case LWJPGP_INIT_SSL:
264 if (arg_to_bool(ctx->buf))
265 a->info->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
267 case LEJPGP_SERVER_STRING:
268 a->info->server_string = a->p;
270 case LEJPGP_PLUGIN_DIR:
271 if (a->count_plugin_dirs == MAX_PLUGIN_DIRS - 1) {
272 lwsl_err("Too many plugin dirs\n");
275 a->plugin_dirs[a->count_plugin_dirs++] = a->p;
278 case LWJPGP_PINGPONG_SECS:
279 a->info->ws_ping_pong_interval = atoi(ctx->buf);
282 case LWJPGP_TIMEOUT_SECS:
283 a->info->timeout_secs = atoi(ctx->buf);
291 a->p += lws_snprintf(a->p, a->end - a->p, "%s", ctx->buf);
298 lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
300 struct jpargs *a = (struct jpargs *)ctx->user;
301 struct lws_protocol_vhost_options *pvo, *mp_cgienv, *headers;
302 struct lws_http_mount *m;
307 lwsl_notice(" %d: %s (%d)\n", reason, ctx->path, ctx->path_match);
308 for (n = 0; n < ctx->wildcount; n++)
309 lwsl_notice(" %d\n", ctx->wild[n]);
312 if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) {
313 /* set the defaults for this vhost */
318 a->info->iface = NULL;
319 a->info->protocols = a->protocols;
320 a->info->extensions = a->extensions;
321 a->info->ssl_cert_filepath = NULL;
322 a->info->ssl_private_key_filepath = NULL;
323 a->info->ssl_ca_filepath = NULL;
324 a->info->client_ssl_cert_filepath = NULL;
325 a->info->client_ssl_private_key_filepath = NULL;
326 a->info->client_ssl_ca_filepath = NULL;
327 a->info->client_ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
328 "ECDHE-RSA-AES256-GCM-SHA384:"
329 "DHE-RSA-AES256-GCM-SHA384:"
330 "ECDHE-RSA-AES256-SHA384:"
331 "HIGH:!aNULL:!eNULL:!EXPORT:"
332 "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"
333 "!SHA1:!DHE-RSA-AES128-GCM-SHA256:"
334 "!DHE-RSA-AES128-SHA256:"
335 "!AES128-GCM-SHA256:"
337 "!DHE-RSA-AES256-SHA256:"
338 "!AES256-GCM-SHA384:"
340 a->info->timeout_secs = 5;
341 a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
342 "ECDHE-RSA-AES256-GCM-SHA384:"
343 "DHE-RSA-AES256-GCM-SHA384:"
344 "ECDHE-RSA-AES256-SHA384:"
345 "HIGH:!aNULL:!eNULL:!EXPORT:"
346 "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"
347 "!SHA1:!DHE-RSA-AES128-GCM-SHA256:"
348 "!DHE-RSA-AES128-SHA256:"
349 "!AES128-GCM-SHA256:"
351 "!DHE-RSA-AES256-SHA256:"
352 "!AES256-GCM-SHA384:"
355 a->info->headers = NULL;
356 a->info->keepalive_timeout = 5;
357 a->info->log_filepath = NULL;
358 a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK |
359 LWS_SERVER_OPTION_STS);
360 a->enable_client_ssl = 0;
363 if (reason == LEJPCB_OBJECT_START &&
364 ctx->path_match == LEJPVP_MOUNTS + 1) {
366 memset(&a->m, 0, sizeof(a->m));
369 /* this catches, eg, vhosts[].ws-protocols[].xxx-protocol */
370 if (reason == LEJPCB_OBJECT_START &&
371 ctx->path_match == LEJPVP_PROTOCOL_NAME + 1) {
372 a->pvo = lwsws_align(a);
373 a->p += sizeof(*a->pvo);
375 n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
376 /* ie, enable this protocol, no options yet */
377 a->pvo->next = a->info->pvo;
378 a->info->pvo = a->pvo;
380 lwsl_notice(" adding protocol %s\n", a->p);
382 a->pvo->value = a->p;
383 a->pvo->options = NULL;
387 /* this catches, eg, vhosts[].headers[].xxx */
388 if (reason == LEJPCB_VAL_STR_END &&
389 ctx->path_match == LEJPVP_HEADERS_NAME + 1) {
390 headers = lwsws_align(a);
391 a->p += sizeof(*headers);
393 n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
394 /* ie, enable this protocol, no options yet */
395 headers->next = a->info->headers;
396 a->info->headers = headers;
397 headers->name = a->p;
398 // lwsl_notice(" adding header %s=%s\n", a->p, ctx->buf);
405 headers->value = a->p;
406 headers->options = NULL;
410 if (reason == LEJPCB_OBJECT_END &&
411 (ctx->path_match == LEJPVP + 1 || !ctx->path[0]) &&
414 struct lws_vhost *vhost;
416 //lwsl_notice("%s\n", ctx->path);
417 if (!a->info->port) {
418 lwsl_err("Port required (eg, 443)");
422 a->info->mounts = a->head;
424 vhost = lws_create_vhost(a->context, a->info);
426 lwsl_err("Failed to create vhost %s\n",
427 a->info->vhost_name);
432 if (a->enable_client_ssl) {
433 const char *cert_filepath = a->info->client_ssl_cert_filepath;
434 const char *private_key_filepath = a->info->client_ssl_private_key_filepath;
435 const char *ca_filepath = a->info->client_ssl_ca_filepath;
436 const char *cipher_list = a->info->client_ssl_cipher_list;
437 memset(a->info, 0, sizeof(*a->info));
438 a->info->client_ssl_cert_filepath = cert_filepath;
439 a->info->client_ssl_private_key_filepath = private_key_filepath;
440 a->info->client_ssl_ca_filepath = ca_filepath;
441 a->info->client_ssl_cipher_list = cipher_list;
442 a->info->options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
443 lws_init_vhost_client_ssl(a->info, vhost);
449 if (reason == LEJPCB_OBJECT_END &&
450 ctx->path_match == LEJPVP_MOUNTS + 1) {
451 static const char * const mount_protocols[] = {
465 if (!a->m.mountpoint || !a->m.origin) {
466 lwsl_err("mountpoint and origin required\n");
469 lwsl_debug("adding mount %s\n", a->m.mountpoint);
471 memcpy(m, &a->m, sizeof(*m));
473 a->last->mount_next = m;
475 for (n = 0; n < ARRAY_SIZE(mount_protocols); n++)
476 if (!strncmp(a->m.origin, mount_protocols[n],
477 strlen(mount_protocols[n]))) {
478 lwsl_err("----%s\n", a->m.origin);
479 m->origin_protocol = n;
480 m->origin = a->m.origin +
481 strlen(mount_protocols[n]);
485 if (n == ARRAY_SIZE(mount_protocols)) {
486 lwsl_err("unsupported protocol:// %s\n", a->m.origin);
498 /* we only match on the prepared path strings */
499 if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
502 switch (ctx->path_match - 1) {
504 a->info->vhost_name = a->p;
507 a->info->port = atoi(ctx->buf);
509 case LEJPVP_INTERFACE:
510 a->info->iface = a->p;
513 if (arg_to_bool(ctx->buf))
514 a->info->options |= LWS_SERVER_OPTION_UNIX_SOCK;
516 a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK);
519 if (arg_to_bool(ctx->buf))
520 a->info->options |= LWS_SERVER_OPTION_STS;
522 a->info->options &= ~(LWS_SERVER_OPTION_STS);
524 case LEJPVP_HOST_SSL_KEY:
525 a->info->ssl_private_key_filepath = a->p;
527 case LEJPVP_HOST_SSL_CERT:
528 a->info->ssl_cert_filepath = a->p;
530 case LEJPVP_HOST_SSL_CA:
531 a->info->ssl_ca_filepath = a->p;
533 case LEJPVP_ACCESS_LOG:
534 a->info->log_filepath = a->p;
536 case LEJPVP_MOUNTPOINT:
537 a->m.mountpoint = a->p;
538 a->m.mountpoint_len = (unsigned char)strlen(ctx->buf);
541 if (!strncmp(ctx->buf, "callback://", 11))
542 a->m.protocol = a->p + 11;
550 case LEJPVP_DEFAULT_AUTH_MASK:
551 a->m.auth_mask = atoi(ctx->buf);
553 case LEJPVP_MOUNT_CACHE_MAX_AGE:
554 a->m.cache_max_age = atoi(ctx->buf);
556 case LEJPVP_MOUNT_CACHE_REUSE:
557 a->m.cache_reusable = arg_to_bool(ctx->buf);
559 case LEJPVP_MOUNT_CACHE_REVALIDATE:
560 a->m.cache_revalidate = arg_to_bool(ctx->buf);
562 case LEJPVP_MOUNT_CACHE_INTERMEDIARIES:
563 a->m.cache_intermediaries = arg_to_bool(ctx->buf);;
565 case LEJPVP_MOUNT_BASIC_AUTH:
566 a->m.basic_auth_login_file = a->p;
568 case LEJPVP_CGI_TIMEOUT:
569 a->m.cgi_timeout = atoi(ctx->buf);
571 case LEJPVP_KEEPALIVE_TIMEOUT:
572 a->info->keepalive_timeout = atoi(ctx->buf);
574 case LEJPVP_CLIENT_CIPHERS:
575 a->info->client_ssl_cipher_list = a->p;
578 a->info->ssl_cipher_list = a->p;
580 case LEJPVP_ECDH_CURVE:
581 a->info->ecdh_curve = a->p;
585 mp_cgienv = lwsws_align(a);
586 a->p += sizeof(*a->m.cgienv);
588 mp_cgienv->next = a->m.cgienv;
589 a->m.cgienv = mp_cgienv;
591 n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
592 mp_cgienv->name = a->p;
594 mp_cgienv->value = a->p;
595 mp_cgienv->options = NULL;
596 //lwsl_notice(" adding pmo / cgi-env '%s' = '%s'\n", mp_cgienv->name,
597 // mp_cgienv->value);
600 case LEJPVP_PROTOCOL_NAME_OPT:
602 * vhosts[].ws-protocols[].xxx-protocol.yyy-option
603 * ie, these are options attached to a protocol with { }
605 pvo = lwsws_align(a);
606 a->p += sizeof(*a->pvo);
608 n = lejp_get_wildcard(ctx, 1, a->p, a->end - a->p);
609 /* ie, enable this protocol, no options yet */
610 pvo->next = a->pvo->options;
611 a->pvo->options = pvo;
618 case LEJPVP_MOUNT_EXTRA_MIMETYPES:
619 a->pvo_em = lwsws_align(a);
620 a->p += sizeof(*a->pvo_em);
622 n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
623 /* ie, enable this protocol, no options yet */
624 a->pvo_em->next = a->m.extra_mimetypes;
625 a->m.extra_mimetypes = a->pvo_em;
626 a->pvo_em->name = a->p;
627 lwsl_notice(" adding extra-mimetypes %s -> %s\n", a->p, ctx->buf);
629 a->pvo_em->value = a->p;
630 a->pvo_em->options = NULL;
633 case LEJPVP_MOUNT_INTERPRET:
634 a->pvo_int = lwsws_align(a);
635 a->p += sizeof(*a->pvo_int);
637 n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p);
638 /* ie, enable this protocol, no options yet */
639 a->pvo_int->next = a->m.interpret;
640 a->m.interpret = a->pvo_int;
641 a->pvo_int->name = a->p;
642 lwsl_notice(" adding interpret %s -> %s\n", a->p,
645 a->pvo_int->value = a->p;
646 a->pvo_int->options = NULL;
649 case LEJPVP_ENABLE_CLIENT_SSL:
650 a->enable_client_ssl = arg_to_bool(ctx->buf);
652 case LEJPVP_CLIENT_SSL_KEY:
653 a->info->client_ssl_private_key_filepath = a->p;
655 case LEJPVP_CLIENT_SSL_CERT:
656 a->info->client_ssl_cert_filepath = a->p;
658 case LEJPVP_CLIENT_SSL_CA:
659 a->info->client_ssl_ca_filepath = a->p;
663 if (arg_to_bool(ctx->buf))
664 a->info->options |= LWS_SERVER_OPTION_DISABLE_IPV6;
666 a->info->options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6);
669 case LEJPVP_IPV6ONLY:
670 a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY;
671 if (arg_to_bool(ctx->buf))
672 a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE;
674 a->info->options &= ~(LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
677 case LEJPVP_SSL_OPTION_SET:
678 a->info->ssl_options_set |= atol(ctx->buf);
680 case LEJPVP_SSL_OPTION_CLEAR:
681 a->info->ssl_options_clear |= atol(ctx->buf);
690 p1 = strstr(p, ESC_INSTALL_DATADIR);
693 if (n > a->end - a->p)
697 a->p += lws_snprintf(a->p, a->end - a->p, "%s", LWS_INSTALL_DATADIR);
698 p += n + strlen(ESC_INSTALL_DATADIR);
701 a->p += lws_snprintf(a->p, a->end - a->p, "%s", p);
708 * returns 0 = OK, 1 = can't open, 2 = parsing error
712 lwsws_get_config(void *user, const char *f, const char * const *paths,
713 int count_paths, lejp_callback cb)
715 unsigned char buf[128];
719 fd = open(f, O_RDONLY);
721 lwsl_err("Cannot open %s\n", f);
724 lwsl_info("%s: %s\n", __func__, f);
725 lejp_construct(&ctx, cb, user, paths, count_paths);
728 n = read(fd, buf, sizeof(buf));
732 m = (int)(signed char)lejp_parse(&ctx, buf, n);
733 } while (m == LEJP_CONTINUE);
740 lwsl_err("%s(%u): parsing error %d: %s\n", f, n, m,
748 #if defined(LWS_USE_LIBUV) && UV_VERSION_MAJOR > 0
751 lwsws_get_config_d(void *user, const char *d, const char * const *paths,
752 int count_paths, lejp_callback cb)
760 ir = uv_loop_init(&loop);
762 lwsl_err("%s: loop init failed %d\n", __func__, ir);
765 if (!uv_fs_scandir(&loop, &req, d, 0, NULL)) {
766 lwsl_err("Scandir on %s failed\n", d);
770 while (uv_fs_scandir_next(&req, &dent) != UV_EOF) {
771 lws_snprintf(path, sizeof(path) - 1, "%s/%s", d, dent.name);
772 ret = lwsws_get_config(user, path, paths, count_paths, cb);
778 uv_fs_req_cleanup(&req);
779 uv_loop_close(&loop);
787 static int filter(const struct dirent *ent)
789 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
797 lwsws_get_config_d(void *user, const char *d, const char * const *paths,
798 int count_paths, lejp_callback cb)
801 struct dirent **namelist;
805 n = scandir(d, &namelist, filter, alphasort);
807 lwsl_err("Scandir on %s failed\n", d);
810 for (i = 0; i < n; i++) {
811 lws_snprintf(path, sizeof(path) - 1, "%s/%s", d,
812 namelist[i]->d_name);
813 ret = lwsws_get_config(user, path, paths, count_paths, cb);
834 lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
838 const char * const *old = info->plugin_dirs;
841 memset(&a, 0, sizeof(a));
845 a.end = (a.p + *len) - 1;
849 info->plugin_dirs = (void *)a.p;
850 a.plugin_dirs = (void *)a.p; /* writeable version */
851 a.p += MAX_PLUGIN_DIRS * sizeof(void *);
853 /* copy any default paths */
855 while (old && *old) {
856 a.plugin_dirs[a.count_plugin_dirs++] = *old;
860 lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d);
861 if (lwsws_get_config(&a, dd, paths_global,
862 ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
864 lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d);
865 if (lwsws_get_config_d(&a, dd, paths_global,
866 ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
869 a.plugin_dirs[a.count_plugin_dirs] = NULL;
878 lwsws_get_config_vhosts(struct lws_context *context,
879 struct lws_context_creation_info *info, const char *d,
885 memset(&a, 0, sizeof(a));
892 a.protocols = info->protocols;
893 a.extensions = info->extensions;
895 lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d);
896 if (lwsws_get_config(&a, dd, paths_vhosts,
897 ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
899 lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d);
900 if (lwsws_get_config_d(&a, dd, paths_vhosts,
901 ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
908 lwsl_err("Need at least one vhost\n");
912 // lws_finalize_startup(context);