Mercurial > hg > batmud > maputils
view mapsearch.c @ 1822:892d5277f1ff
Remove note about the search pattern parser being not very tolerant, it's somewhat better now.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 30 Oct 2017 12:55:21 +0200 |
parents | 95d7108f52e2 |
children | 599ef33b918d |
line wrap: on
line source
/* * PupuMaps Search WebSockets server * Written by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2017 Tecnic Software productions (TNSP) */ #include "th_args.h" #include "th_config.h" #include "liblocfile.h" #include "libmaputils.h" #include <libwebsockets.h> /* Default settings etc. constants */ #define SET_MAX_MAPS 16 // Maximum number of maps allowed to be loaded #define SET_MAX_LISTEN 4 // Maximum number of interfaces to listen #define SET_MAX_MATCHES 64 // Maximum number of match results per query // List of default SSL/TLS ciphers to use/allowed #define SET_DEF_CIPHERS \ "ECDHE-ECDSA-AES256-GCM-SHA384:" \ "ECDHE-RSA-AES256-GCM-SHA384:" \ "DHE-RSA-AES256-GCM-SHA384:" \ "ECDHE-RSA-AES256-SHA384:" \ "HIGH:!aNULL:!eNULL:!EXPORT:" \ "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:" \ "!SHA1:!DHE-RSA-AES128-GCM-SHA256:" \ "!DHE-RSA-AES128-SHA256:" \ "!AES128-GCM-SHA256:" \ "!AES128-SHA256:" \ "!DHE-RSA-AES256-SHA256:" \ "!AES256-GCM-SHA384:" \ "!AES256-SHA256" /* Structure that holds information about one listen interface */ typedef struct { char *interface; // Listen interface (* = listen all) char *vhostname; // Vhost name int port; // Port number BOOL useIPv6; BOOL useSSL; // Use SSL/TLS? char *sslCertFile, // Certificate file *sslKeyFile, // Key file *sslCAFile; // CA file struct lws_vhost *vhost; // LWS vhost info } MAPListenerCtx; /* Structure for holding information about one map and its locations */ typedef struct { char *mapFilename; // Filename of the map data LocFileInfo locFile; // Location file info struct MapBlock *map; // Map data, when loaded MapLocations loc; // Map locations, when loaded } MAPInfoCtx; typedef struct { char *map; int mx, my, wx, wy; double accuracy; BOOL centered; } MAPMatch; /* Options */ MAPInfoCtx optMaps[SET_MAX_MAPS]; MAPListenerCtx *optListenTo[SET_MAX_LISTEN]; int optNMaps = 0; int optNListenTo = 0; char *optCleanChars = " *@?%C"; char *optSSLCipherList = SET_DEF_CIPHERS; struct lws_context *setLWSContext = NULL; int optWorldXC = 0, optWorldYC = 0; /* Arguments */ static const th_optarg optList[] = { { 0, '?', "help", "Show this help and be so very much verbose that it almost hurts you", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, { 2, 'l', "listen", "Listen to interface (see below)", OPT_ARGREQ }, { 3, 0, "ssl-ciphers", "Specify list of SSL/TLS ciphers", OPT_ARGREQ }, { 4, 0, "clean-chars", "String of characters to 'clean up' from map blocks.", OPT_ARGREQ }, { 5, 'w', "world-origin", "Specify the world origin <x:y> which map offsets relate to.", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp(void) { th_print_banner(stdout, th_prog_name, "[options] <map spec>"); th_args_help(stdout, optList, optListN, 0); fprintf(stdout, "\n" "Listening interface(s) are specified with following syntax:\n" "-l \"[@]<interface/IP/host>:<port>[=<SSL/TLS spec>]\"\n" "\n" "IPv6 addresses should be specified with the square bracket notation.\n" "To force IPv6, use IPv6 address [] or prefix spec with @. In order to\n" "listen to all interfaces, you can specify an asterisk (*) as host. Example:\n" "\n" "-l *:3491 -l *:3492=<vhostname for SNI>:<ssl_cert_file.crt>:<ssl_key_file.key>:<ca_file.crt>\n" "\n" "Would listen for normal WebSocket (ws://) connections on port 3491 and for\n" "secure SSL/TLS WebSocket (wss://) connections on port 3492 of all interfaces.\n" "\n" "Maps and location files for each map are specified as follows:\n" "<filename.map>:<locfilename.loc>:<map/continent name>[:<world x-offset>:<world y-offset>]\n" "\n" "All the map offsets are relative to world origin coordinates, which are 0,0 by default.\n" "-w 8192:8192\n" ); } BOOL mapParseCoordPair(const char *str, int *xc, int *yc) { char *piece, *tmp, *fmt = th_strdup(str); BOOL ret = FALSE; if ((piece = strchr(fmt, ':')) == NULL) goto err; *piece++ = 0; tmp = th_strdup_trim(fmt, TH_TRIM_BOTH); *xc = atoi(tmp); th_free(tmp); tmp = th_strdup_trim(piece, TH_TRIM_BOTH); *yc = atoi(tmp); th_free(tmp); ret = TRUE; err: th_free(fmt); return ret; } BOOL mapParseMapSpec(const char *str, MAPInfoCtx *info) { char *piece, *start, *fmt = th_strdup(str); BOOL ret = FALSE; // Check for map filename end if ((piece = strchr(fmt, ':')) == NULL) goto err; *piece++ = 0; info->mapFilename = th_strdup_trim(fmt, TH_TRIM_BOTH); start = piece; // Check for loc filename end if ((piece = strchr(start, ':')) == NULL) goto err; *piece++ = 0; info->locFile.filename = th_strdup_trim(start, TH_TRIM_BOTH); start = piece; // Check for world x-offset separator if ((piece = strchr(start, ':')) != NULL) *piece++ = 0; info->locFile.continent = th_strdup_trim(start, TH_TRIM_BOTH); // Get world X/Y offsets, if any if (piece != NULL && !mapParseCoordPair(piece, &info->locFile.x, &info->locFile.y)) goto err; ret = TRUE; err: th_free(fmt); return ret; } MAPListenerCtx *mapNewListenCtx(void) { return th_malloc0(sizeof(MAPListenerCtx)); } void mapFreeListenCtxR(MAPListenerCtx *ctx) { if (ctx != NULL) { th_free(ctx->interface); th_free(ctx->sslCertFile); th_free(ctx->sslKeyFile); th_free(ctx->sslCAFile); } } void mapFreeListenCtx(MAPListenerCtx *ctx) { mapFreeListenCtxR(ctx); th_free(ctx); } MAPListenerCtx *mapParseListenerSpec(const char *cfmt) { char *start, *end, *flags, *port = NULL, *interface, *fmt = th_strdup(cfmt); BOOL ret = FALSE; MAPListenerCtx *ctx; if ((ctx = mapNewListenCtx()) == NULL) goto out; interface = fmt; if (*interface == '@') { interface++; ctx->useIPv6 = TRUE; } if (*interface == '[') { // IPv6 IP address is handled in a special case interface++; if ((end = strchr(interface, ']')) == NULL) { THERR("Invalid IPv6 IP address '%s'.\n", cfmt); goto out; } *end++ = 0; ctx->useIPv6 = TRUE; } else { end = strchr(interface, ':'); } // Find port number separator if (end == NULL || *end != ':') { THERR("Missing listening port in '%s'.\n", cfmt); goto out; } *end++ = 0; start = end; // Check for '=flags' at the end if ((flags = strchr(start, '=')) != NULL) *flags++ = 0; // Get the interface name ctx->interface = th_strdup_trim(interface, TH_TRIM_BOTH); if (strcmp(ctx->interface, "*") == 0) { th_free(ctx->interface); ctx->interface = NULL; } // Get port number if ((port = th_strdup_trim(start, TH_TRIM_BOTH)) == NULL) { THERR("Missing listening port in '%s'.\n", cfmt); goto out; } ctx->port = atoi(port); // Parse the SSL/TLS spec, if any if (flags != NULL) { char *cstart, *cend; // Check for separator cstart = flags; if ((cend = strchr(cstart, ':')) == NULL) { THERR("Invalid SSL/TLS spec '%s'\n", flags); goto out; } *cend++ = 0; // Get the vhost name ctx->vhostname = th_strdup_trim(cstart, TH_TRIM_BOTH); if (strlen(ctx->vhostname) == 0) { th_free(ctx->vhostname); ctx->vhostname = NULL; } // Check for separator cstart = cend; if ((cend = strchr(cend, ':')) == NULL) { THERR("Invalid SSL/TLS spec, missing certificate file.\n"); goto out; } *cend++ = 0; // Get certificate file path ctx->sslCertFile = th_strdup_trim(cstart, TH_TRIM_BOTH); // Check for separator cstart = cend; if ((cend = strchr(cend, ':')) == NULL) { THERR("Invalid SSL/TLS spec, missing key file.\n"); goto out; } *cend++ = 0; // Get the rest ctx->sslKeyFile = th_strdup_trim(cstart, TH_TRIM_BOTH); ctx->sslCAFile = th_strdup_trim(cend, TH_TRIM_BOTH); ctx->useSSL = TRUE; } // Check for duplicates for (int n = 0; n < optNListenTo; n++) { MAPListenerCtx *chk = optListenTo[n]; if (( (ctx->interface == NULL && chk->interface == NULL) || (ctx->interface != NULL && chk->interface != NULL && strcmp(ctx->interface, chk->interface) == 0)) && chk->port == ctx->port) { THERR("Duplicate listener spec (%s:%d)\n", ctx->interface != NULL ? ctx->interface : "*", ctx->port); goto out; } } ret = TRUE; out: th_free(fmt); th_free(port); if (ret) return ctx; mapFreeListenCtx(ctx); return NULL; } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: th_verbosityLevel++; break; case 2: if (optNListenTo < SET_MAX_LISTEN) { MAPListenerCtx *ctx; if ((ctx = mapParseListenerSpec(optArg)) != NULL) optListenTo[optNListenTo++] = ctx; else return FALSE; } else { THERR("Maximum number of listener specs already specified.\n"); return FALSE; } break; case 3: optSSLCipherList = optArg; break; case 4: optCleanChars = optArg; break; case 5: if (!mapParseCoordPair(optArg, &optWorldXC, &optWorldYC)) { THERR("Invalid world origin coordinates '%s'.\n", optArg); return FALSE; } break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (optNMaps < SET_MAX_MAPS) { if (!mapParseMapSpec(currArg, &optMaps[optNMaps])) { THERR("Invalid map spec '%s'.\n", currArg); return FALSE; } optNMaps++; return TRUE; } else { THERR("Maximum number of map specs already specified.\n"); return FALSE; } } void mapSigHandler(uv_signal_t *watcher, int signum) { (void) signum; THERR("Signal %d caught, exiting...\n", watcher->signum); switch (watcher->signum) { case SIGTERM: case SIGINT: break; default: signal(SIGABRT, SIG_DFL); abort(); break; } lws_libuv_stop(setLWSContext); } void mapBlockGetDimensions(const unsigned char *data, const size_t len, int *width, int *height) { size_t offs = 0; int x = 0, x2 = 0; *width = *height = 0; while (offs < len) { const unsigned char ch = data[offs++]; if (ch == '\n') { if (x > *width) *width = x; (*height)++; x = x2 = 0; } else { x2++; if (ch != ' ') x = x2; } } if (x > *width) *width = x; if (x > 0) (*height)++; } BOOL mapBlockParse(const unsigned char *data, const size_t len, MapBlock *res) { size_t offs = 0; for (int yc = 0; yc < res->height; yc++) { unsigned char *dp = res->data + (yc * res->scansize); if (offs < len && data[offs] != '\n') for (int xc = 0; xc < res->width; xc++) { if (offs < len) dp[xc] = data[offs++]; else goto out; } while (offs < len && data[offs] != '\n') offs++; if (offs < len && data[offs] == '\n') offs++; } out: return offs == len; } BOOL mapStrChr(const unsigned char *symbols, const size_t nsymbols, const unsigned char ch) { for (size_t n = 0; n < nsymbols; n++) if (symbols[n] == ch) return TRUE; return FALSE; } BOOL mapBlockFindCenter(const MapBlock *block, const unsigned char *symbols, const size_t nsymbols, int *cx, int *cy, const int tolerance) { const int x0 = (block->width * tolerance) / 100, x1 = (block->width * (100 - tolerance)) / 100, y0 = (block->height * tolerance) / 100, y1 = (block->height * (100 - tolerance)) / 100; *cx = *cy = 0; for (int yc = 0; yc < block->height; yc++) { unsigned char *dp = block->data + (yc * block->scansize); for (int xc = 0; xc < block->width; xc++) { if (xc >= x0 && xc <= x1 && yc >= y0 && yc <= y1 && mapStrChr(symbols, nsymbols, dp[xc])) { *cx = xc; *cy = yc; return TRUE; } } } return FALSE; } int mapBlockGetEntropy(const MapBlock *map, const char *exclude, const int nexclude) { unsigned char *list; int num, i; // Allocate memory for entropy array if ((list = th_malloc0(256)) == NULL) return -1; // Collect sums into entropy array for (int y = 0; y < map->height; y++) { unsigned char *c = map->data + (y * map->scansize); for (int x = 0; x < map->width; x++) { list[*c]++; c++; } } // Handle exclusions if (exclude != NULL && nexclude > 0) { for (i = 0; i < nexclude; i++) list[(int) exclude[i]] = 0; } // Calculate simple entropy for (num = 0, i = 0; i < 256; i++) if (list[i]) num++; th_free(list); return num; } double mapMatchBlock(const MapBlock *map, const MapBlock *match, int ox, int oy, int fillCh, BOOL hardDrop) { int n, k; n = k = 0; for (int y = 0; y < match->height; y++) { unsigned char *dp = match->data + (y * match->scansize); for (int x = 0; x < match->width; x++) { int c1, c2; const int dx = ox + x, dy = oy + y; k++; if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height) c2 = map->data[(dy * map->scansize) + dx]; else c2 = fillCh; c1 = dp[x]; if (c1 == 0 || c1 == c2) n++; else if (hardDrop) return -1; } } if (k > 0) return ((double) n * 100.0f) / (double) k; else return 0.0f; } void mapPerformSearch(struct lws *wsi, unsigned char *data, const size_t len, char **verr) { int width, height, centerX, centerY; MapBlock *pattern = NULL; BOOL mapList[SET_MAX_MAPS]; MAPMatch matches[SET_MAX_MATCHES]; int nmatches = 0, maxMatches, nmapList; BOOL centerFound; size_t offs; // Get requested number of matches for (offs = 0; offs < len && data[offs] != ':';) offs++; if (data[offs] != ':' || offs >= len) { *verr = "Invalid search query."; goto out; } data[offs++] = 0; maxMatches = atoi((char *) data); THMSG(0, "Requested %d matches.\n", maxMatches); if (maxMatches < 1 || maxMatches > SET_MAX_MATCHES) maxMatches = SET_MAX_MATCHES; // Get active map list nmapList = 0; memset(&mapList, 0, sizeof(mapList)); while (offs < len) { size_t offs2; BOOL found = FALSE; // Find next separator or end \n for (offs2 = offs; offs2 < len && data[offs2] != ':' && data[offs2] != '\n';) offs2++; // Check against map list if (offs2 > offs) for (int nmap = 0; nmap < optNMaps && !found; nmap++) { const char *name = optMaps[nmap].locFile.continent; const size_t slen = strlen(name); if (offs2 >= offs + slen && memcmp(data + offs, name, slen) == 0) { mapList[nmap] = TRUE; nmapList++; found = TRUE; } } if (!found) { *verr = "Unknown map spec."; goto out; } // Check for separator or end if (data[offs2] != ':') { offs = offs2; break; } else offs = offs2 + 1; } // Check for remaining data if (offs + 1 >= len || data[offs] != '\n') { *verr = "No map pattern data!"; goto out; } offs++; // Parse pattern block dimensions mapBlockGetDimensions(data + offs, len - offs, &width, &height); if (width <= 0 || height <= 0) { *verr = "Could not parse map block dimensions."; goto out; } if (width * height < 3) { *verr = "Search block pattern too small."; goto out; } if ((pattern = mapBlockAlloc(width, height)) == NULL) goto out; if (!mapBlockParse(data + offs, len - offs, pattern)) { *verr = "Error parsing map block data."; goto out; } // Entropy check int entropy = mapBlockGetEntropy(pattern, optCleanChars, strlen(optCleanChars)); if ((entropy < 2 && width < 10 && height < 10) || (entropy < 3 && width * height < 6)) { *verr = "Search block entropy insufficient."; goto out; } // Find pattern center marker, if any centerFound = mapBlockFindCenter(pattern, (unsigned char*) "*@", 2, ¢erX, ¢erY, 20); // Clean the pattern from characters we do not want to match mapBlockClean(pattern, optCleanChars); // // Search the maps .. enabled or if none specified, all of them // for (int nmap = 0; nmap < optNMaps; nmap++) if (mapList[nmap] || nmapList == 0) { MAPInfoCtx *info = &optMaps[nmap]; for (int oy = 0; oy < info->map->height - height - 1; oy++) for (int ox = 0; ox < info->map->width - width - 1; ox++) { // Check for match double accuracy = mapMatchBlock(info->map, pattern, ox, oy, 0, TRUE); if (accuracy > 99.5f) { // Okay, add the match to our list MAPMatch *match = &matches[nmatches++]; match->map = info->locFile.continent; match->mx = ox + centerX; match->my = oy + centerY; match->wx = optWorldXC + info->locFile.x + ox + centerX; match->wy = optWorldYC + info->locFile.y + oy + centerY; match->accuracy = accuracy; match->centered = centerFound; // Check for max matches if (nmatches >= maxMatches) goto out; } } } out: mapBlockFree(pattern); // If an error occured, bail out now if (*verr != NULL) return; // We got some matches, output them as a JSON array char *buf = NULL; size_t bufLen = 0, bufSize = 0; th_strbuf_puts(&buf, &bufSize, &bufLen, "RESULT:["); for (int n = 0; n < nmatches; n++) { MAPMatch *match = &matches[n]; char *vstr = th_strdup_printf( "[%1.2f,%d,\"%s\",%d,%d,%d,%d]%s", match->accuracy, match->centered, match->map, match->mx, match->my, match->wx, match->wy, (n < nmatches - 1) ? "," : ""); th_strbuf_puts(&buf, &bufSize, &bufLen, vstr); th_free(vstr); } th_strbuf_puts(&buf, &bufSize, &bufLen, "]"); lws_write(wsi, (unsigned char *) buf, bufLen, LWS_WRITE_TEXT); th_free(buf); } int mapLWSCallback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { (void) user; switch (reason) { case LWS_CALLBACK_CLIENT_WRITEABLE: THPRINT(0, "Connection established.\n"); break; case LWS_CALLBACK_RECEIVE: { char *data = (char *) in; unsigned char *udata = (unsigned char *) in; char *verr = NULL; // Check what the request is about? if (len >= 10 && strncmp(data, "MAPSEARCH:", 10) == 0) { // A map search query! if (len <= 10 + 1) verr = "Invalid search query."; else mapPerformSearch(wsi, udata + 10, len - 10, &verr); } else if (len >= 7 && strncmp(data, "GETMAPS", 7) == 0) { // Client wants a list of available maps char *buf = NULL; size_t bufLen = 0, bufSize = 0; th_strbuf_puts(&buf, &bufSize, &bufLen, "MAPS:["); for (int n = 0; n < optNMaps; n++) { MAPInfoCtx *info = &optMaps[n]; char *vstr = th_strdup_printf( "[\"%s\",%d,%d]%s", info->locFile.continent, info->locFile.x + optWorldXC, info->locFile.y + optWorldYC, (n < optNMaps - 1) ? "," : ""); th_strbuf_puts(&buf, &bufSize, &bufLen, vstr); th_free(vstr); } th_strbuf_puts(&buf, &bufSize, &bufLen, "]"); lws_write(wsi, (unsigned char *) buf, bufLen, LWS_WRITE_TEXT); } else { // Unknown or invalid query verr = "Invalid command/search query, not enough data."; } // Check for errors .. if (verr != NULL) { THERR("%s\n", verr); char *vstr = th_strdup_printf("ERROR:%s", verr); lws_write(wsi, (unsigned char *) vstr, strlen(vstr), LWS_WRITE_TEXT); th_free(vstr); } // End communication verr = "END"; lws_write(wsi, (unsigned char *) verr, strlen(verr), LWS_WRITE_TEXT); lws_close_reason(wsi, LWS_CLOSE_STATUS_NOSTATUS, NULL, 0); } break; default: break; } return 0; } static const struct lws_extension mapLWSExtensions[] = { { "permessage-deflate", lws_extension_callback_pm_deflate, "permessage-deflate" }, { "deflate-frame", lws_extension_callback_pm_deflate, "deflate_frame" }, { NULL, NULL, NULL } }; static const struct lws_protocols mapLWSProtocols[] = { { "default", &mapLWSCallback, 0, 0, 0, NULL }, { NULL, NULL, 0, 0, 0, NULL } }; int main(int argc, char *argv[]) { // Initialize th_init("MapSearch", "Map Search WebSockets server", "0.1", NULL, NULL); th_verbosityLevel = 0; memset(&optMaps, 0, sizeof(optMaps)); memset(&optListenTo, 0, sizeof(optListenTo)); // Parse command line arguments BOOL argsOK = th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, 0); if (!argsOK) return -2; if (optNMaps == 0) { THERR("No maps specified.\n"); goto exit; } if (optNListenTo == 0) { THERR("No listeners specified.\n"); goto exit; } // Load maps THMSG(1, "Trying to load %d map specs ..\n", optNMaps); for (int n = 0; n < optNMaps; n++) { MAPInfoCtx *info = &optMaps[n]; FILE *fh; THMSG(1, "Map ID '%s', data '%s', locations '%s' @ %d, %d\n", info->locFile.continent, info->mapFilename, info->locFile.filename, info->locFile.x, info->locFile.y); if ((info->map = mapBlockParseFile(info->mapFilename, FALSE)) == NULL) { THERR("Could not read map data file '%s'.\n", info->mapFilename); goto exit; } if ((fh = fopen(info->locFile.filename, "rb")) == NULL) { THERR("Could not open location file '%s' for reading.\n", info->locFile.filename); goto exit; } if (!locParseLocStream(fh, &info->locFile, &(info->loc), info->locFile.x, info->locFile.y)) { fclose(fh); goto exit; } fclose(fh); } // Initialize libwebsockets and create context THMSG(1, "Creating libwebsockets context.\n"); MAPListenerCtx *ctx = optListenTo[0]; struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); info.port = ctx->port; info.iface = ctx->interface; info.max_http_header_pool = 16; info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_LIBUV; // do we need this? info.protocols = mapLWSProtocols; info.extensions = mapLWSExtensions; info.ssl_cipher_list = optSSLCipherList; info.timeout_secs = 5; if ((setLWSContext = lws_create_context(&info)) == NULL) { THERR("libwebsocket initialization failed.\n"); goto exit; } // Create listener LWS vhosts .. for (int n = 0; n < optNListenTo; n++) { ctx = optListenTo[n]; if (ctx->useSSL) { THMSG(1, "Listen to %s:%d (wss) [vhost='%s', cert='%s', key='%s', ca='%s']\n", ctx->interface != NULL ? ctx->interface : "*", ctx->port, ctx->vhostname, ctx->sslCertFile, ctx->sslKeyFile, ctx->sslCAFile); info.vhost_name = ctx->vhostname; info.ssl_cert_filepath = ctx->sslCertFile; info.ssl_private_key_filepath = ctx->sslKeyFile; info.ssl_ca_filepath = ctx->sslCAFile[0] ? ctx->sslCAFile : NULL; } else { THMSG(1, "Listen to %s:%d (ws)\n", ctx->interface != NULL ? ctx->interface : "*", ctx->port); info.vhost_name = NULL; info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.ssl_ca_filepath = NULL; } info.port = ctx->port; info.iface = ctx->interface; if ((ctx->vhost = lws_create_vhost(setLWSContext, &info)) == NULL) { THERR("LWS vhost creation failed!\n"); goto exit; } } // Set up signal handlers THMSG(1, "Setting up signal handlers.\n"); lws_uv_sigint_cfg(setLWSContext, 1, mapSigHandler); THMSG(1, "Waiting for connections...\n"); if (lws_uv_initloop(setLWSContext, NULL, 0)) { THERR("lws_uv_initloop() failed.\n"); goto exit; } // Start running .. lws_libuv_run(setLWSContext, 0); exit: // Shutdown sequence THMSG(1, "Server shutting down.\n"); if (setLWSContext != NULL) lws_context_destroy(setLWSContext); for (int n = 0; n < optNListenTo; n++) mapFreeListenCtx(optListenTo[n]); for (int n = 0; n < optNMaps; n++) { MAPInfoCtx *info = &optMaps[n]; th_free(info->mapFilename); th_free(info->locFile.filename); th_free(info->locFile.continent); mapBlockFree(info->map); locFreeMapLocations(&info->loc); } return 0; }