/*****************************************************************************/ /* GeoLocate.c A utility to geolocate an IP address from a (semi-)free service. Four to choose from (defaults to the first, which is the best IMHO): http://ip-api.com/ http://ipwhois.app/ http://www.geoplugin.net/ http://geolocation-db.com/ Uses a geolocation cache to minimise lookups. IP-API.COM RATE LIMIT --------------------- "This endpoint is limited to 45 requests per minute from an IP address. If you go over the limit your requests will be throttled (HTTP 429) until your rate limit window is reset. If you constantly go over the limit your IP address will be banned for 1 hour." GeoLocation will either be steady (e.g. 1 every second or two, e.g. HTTPDMON) or bursty (multiple being resolved at once, e.g. INTRUspect, QdLogStats). Steady will be unlikely to exceed the threshold and therefore not needing explicit rate-limiting. If the usage retries non-resolved geolocations (e.g. INTRUspect) then a no-delay option can be used. NoDelay use requires setting GeoLocateNoDelay(1) to indicate this. It also can be set 0 (the default) to emphasize it is using a delay. If the rate limit delays a result the call returns "(waiting...)". By default each call should return a result and so some calls may be delayed to meet rate limits. Where a call does not require a response it returns immediately with an empty string (""). To disable the default rate limit delay call GeoLocateDelayed(0) before processing. STANDALONE ---------- The utility can be used standalone where the default behaviour is to create DCL symbols containing the location data. By default, symbols are scoped global. Using the "/local" qualifier they can be created as local symbols. Best to delete existing symbols to avoid "hangovers" from previous geolocations. For example: $ delete /symbol /global /all $ mcr cgi-bin:[000000]geolocate 81.39.111.122 $ show sym geolocate* GEOLOCATE_ADDR == "81.39.111.122" GEOLOCATE_CITY == "Madrid" GEOLOCATE_COUNTRY == "Spain" GEOLOCATE_ERROR == "" GEOLOCATE_HOST == "122.red-81-39-111.dynamicip.rima-tde.net" GEOLOCATE_LAT == "40.4163" GEOLOCATE_LONG == "-3.6934" GEOLOCATE_MILLISECS == "103" GEOLOCATE_REGION == "Madrid" GEOLOCATE_SERVICE == "ip-api.com" This is the default behaviour if used passinf the lookup as a parameter. CGI/CGIPLUS ----------- If the lookup is NOT passed as a parameter the query string may optionally be used to pass a host name or address. If not it defaults to the remote host name or address of the client. Used as CGI the rate limit may quickly be exceeded on a busy site. As a CGIplus script the cache is maintained meaning the rate-limit is less likely to be quickly exceeded. Also CGIplus is an order of magnitude more responsive. Logical name GEOLOCATE_SERVICE provides same function as /SERVICE qualifier. OBJECT CODE ----------- This utility can be used standalone or as an object module utilised by another utility. HTTPDMON.C and QDLOGSTATS.C both have optional geolocation builds. GEOLOCATE_UTF8 -------------- GeoLocate is intended for VT terminals. It has UTF-8 characters munged into an unlikely ASCII character. Defining the above logical name to "1" results in supression of this. This is intended for web pages with a charset=utf-8 content-type. *NOT* for VT terminals. If defined as a digit then 0 disables the munge (i.e. enables UTF-8) and 1 enables UTF-8. If defined as a non-digit character then that is used as the UTF-8 substition character. The default is '~' (tilde). The /UTF8 qualifier performs the same function at the command-line. BUILD DETAILS ------------- See BUILD_GEOLOCATE.COM procedure. VERSION LOG ----------- 20-OCT-2024 MGD v2.0.0, CGI and CGIplus 26-FEB-2022 MGD initial */ /*****************************************************************************/ #define SOFTWAREVN "2.0.0" #define SOFTWARENM "GEOLOCATE" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif #ifndef GEOLOCATE_OBJECT #define GEOLOCATE_OBJECT 0 #endif #ifndef GEOLOCATE_OPENSSL #define GEOLOCATE_OPENSSL 0 #endif #ifndef GEOLOCATE_SYMBOL #define GEOLOCATE_SYMBOL 1 #endif /* ensure BSD 4.4 structures */ #define _SOCKADDR_LEN /* BUT MultiNet BG driver does not support BSD 4.4 AF_INET addresses */ #define NO_SOCKADDR_LEN_4 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if GEOLOCATE_OPENSSL #include #include #include #endif /* GEOLOCATE_OPENSSL */ #include "geolocate.h" const static char Utility [] = SOFTWARENM, SoftwareId [] = SOFTWAREID; #if GEOLOCATE_OBJECT #undef GEOLOCATE_SYMBOL #define GEOLOCATE_SYMBOL 0 extern int Debug; #else int Debug = 0; #include "cgilib.h" #endif #define TIME64_ONE_SEC ((int64)10000000) static char *GeoServices [] = { "ip-api.com", "ipwhois.app", "www.geoplugin.net", "geolocation-db.com", NULL }; static int GeoBurstCount, GeoBurstLimit = 10, GeoBurstPeriod = 10, GeoDelaySeconds, GeoIsDelayed = 1, GeoIsNoDelay, GeoSymbolScope = LIB$K_CLI_GLOBAL_SYM, GeoIsUtf8, IsCgiPlus; static int64 GeoBurstTime64, GeoCurrentTime64, GeoTotalMilliSecs; static char *GeoServicePtr, *GeoSubsChar = "~"; static int GeoIsAddress (char*); static void GeoLocateMungeUniEsc (char*); static void GeoLocateMungeUtf8 (char*); static void GeoResponseSymbols (); static void GeoSetSymbol (char*, char*, int); static char* GeoTrnLnm (char*, char*, int); int GeoLocateFailCount; int GeoLocateHitCount; int GeoLocateLookupCount; int GeoLocateMilliSecs; int GeoLocateAveMilliSecs; int GeoLocateMaxMilliSecs; int GeoLocateMinMilliSecs; char *GeoLocateService; /*****************************************************************************/ /* */ #if !GEOLOCATE_OBJECT void CgiLocate (); int main (int argc, char *argv[]) { int cnt, idx, loop, full, out; char *aservice, *alookup, *cptr, *sptr; /*********/ /* begin */ /*********/ CgiLibEnvironmentInit (argc, argv, 0); if (IsCgiPlus = CgiLibEnvironmentIsCgiPlus()) CgiLocate (); full = out = 0; loop = 1; alookup = aservice = NULL; for (cnt = 1; cnt < argc; cnt++) { if (cnt == 1) { alookup = argv[cnt]; continue; } if (!strcasecmp (argv[cnt], "/stdout")) out = 1; else if (!strcasecmp (argv[cnt], "/debug")) Debug = 1; else if (!strcasecmp (argv[cnt], "/dbug")) Debug = 1; else if (!strcasecmp (argv[cnt], "/full")) full = 1; else if (!strcasecmp (argv[cnt], "/local")) GeoSymbolScope = LIB$K_CLI_LOCAL_SYM; else if (!strcasecmp (argv[cnt], "/utf8")) GeoLocateUtf8 (1); else if (!strcasecmp (argv[cnt], "/utf8=1")) GeoLocateUtf8 (1); else if (!strncasecmp (argv[cnt], "/utf8=", 6) && argv[cnt][6]) GeoSubsChar = argv[cnt]+6; else if (!strncasecmp (argv[cnt], "/service=", 9)) aservice = argv[cnt] + 9; else if (!strcasecmp (argv[cnt], "/version")) fprintf (stdout, "%s\n", GeoLocateSoftwareId()); else if (isdigit(argv[cnt][0])) loop = atoi(argv[cnt]); } if (!alookup) CgiLocate (); if (GeoTrnLnm ("GEOLOCATE_DEBUG", NULL, 0) != NULL) Debug = 1; if ((cptr = GeoTrnLnm ("GEOLOCATE_UTF8", NULL, 0)) != NULL) { if (isdigit(*cptr) && *cptr != '0') GeoLocateUtf8 (1); if (!isdigit(*cptr)) GeoSubsChar = strdup(cptr); } if (argc > 1 && !strcasecmp (argv[1], "/version")) fprintf (stdout, "%s\n", GeoLocateSoftwareId()); for (cnt = 1; cnt <= loop; cnt++) { while (sptr = GeoLocate (aservice, alookup)) { GeoSetSymbol ("GEOLOCATE_ADDR", "", 0); GeoSetSymbol ("GEOLOCATE_CITY", "", 0); GeoSetSymbol ("GEOLOCATE_COUNTRY", "", 0); GeoSetSymbol ("GEOLOCATE_ERROR", "", 0); GeoSetSymbol ("GEOLOCATE_HOST", "", 0); GeoSetSymbol ("GEOLOCATE_LAT", "", 0); GeoSetSymbol ("GEOLOCATE_LONG", "", 0); GeoSetSymbol ("GEOLOCATE_MILLISECS", NULL, GeoLocateMilliSecs); GeoSetSymbol ("GEOLOCATE_REGION", "", 0); GeoSetSymbol ("GEOLOCATE_SERVICE", GeoLocateService, 0); if (GeoIsAddress (alookup)) { GeoSetSymbol ("GEOLOCATE_ADDR", alookup, 0); cptr = GeoLocateLookup (NULL, alookup); if (*cptr == '[') GeoSetSymbol ("GEOLOCATE_ERROR", cptr, 0); else GeoSetSymbol ("GEOLOCATE_HOST", cptr, 0); } else { GeoSetSymbol ("GEOLOCATE_HOST", alookup, 0); cptr = GeoLocateLookup (alookup, NULL); if (*cptr == '[') GeoSetSymbol ("GEOLOCATE_ERROR", cptr, 0); else GeoSetSymbol ("GEOLOCATE_ADDR", cptr, 0); } if (sptr && *sptr == '[') { GeoSetSymbol ("GEOLOCATE_ERROR", sptr, 0); if (out) fprintf (stdout, "*** %s ***\n", sptr); } else { if (out) fprintf (stdout, "|%s| %dms lookup:%d hit:%d\n", sptr, GeoLocateMilliSecs, GeoLocateLookupCount, GeoLocateHitCount); cptr = sptr; while (*cptr && *cptr != GEOSEP) cptr++; if (*cptr) cptr++; for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_COUNTRY", sptr, 0); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_REGION", sptr, 0); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_CITY", sptr, 0); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_LAT", sptr, 0); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_LONG", sptr, 0); *cptr++ = GEOSEP; } } if (full) GeoResponseSymbols (); if (!aservice || *aservice != '*') break; GeoLocateResponse (NULL, NULL, NULL); } } } #endif /* GEOLOCATE_OBJECT */ /*****************************************************************************/ /* Executing as a CGI or CGIplus script. */ #if !GEOLOCATE_OBJECT void CgiLocate () { char *aservice, *aptr, *cptr, *eptr, *hptr, *qptr, *sptr; char buf [256]; /*********/ /* begin */ /*********/ if ((cptr = GeoTrnLnm ("GEOLOCATE_UTF8", NULL, 0)) != NULL) { if (isdigit(*cptr) && *cptr != '0') GeoLocateUtf8 (1); if (!isdigit(*cptr)) GeoSubsChar = strdup(cptr); } for (;;) { /* block waiting for the next request */ if (IsCgiPlus) CgiLibVar (""); if (GeoTrnLnm ("GEOLOCATE_DEBUG", NULL, 0) != NULL) { Debug = 1; fprintf (stdout, "content-type: text/plain\r\n\r\n"); fflush(stdout); } aservice = GeoTrnLnm ("GEOLOCATE_SERVICE", NULL, 0); setenv ("ADDR", "", 1); setenv ("CITY", "", 1); setenv ("COUNTRY", "", 1); setenv ("ERROR", "", 1); setenv ("HOST", "", 1); setenv ("LAT", "", 1); setenv ("LONG", "", 1); setenv ("REGION", "", 1); aptr = eptr = hptr = qptr = sptr = NULL; if (sptr = CgiLibVarNull ("PATH_TRANSLATED")) { /* is this call from SSI document? */ for (cptr = sptr; *cptr; *cptr++); while (cptr > sptr && *cptr != '.') cptr--; sptr = NULL; /* if no then interrogate query string */ if (strcasecmp (cptr, ".shtml")) if (sptr = qptr = CgiLibVarNull ("QUERY_STRING")) if (!*sptr) sptr = NULL; } if (!(aptr = CgiLibVarNull ("REMOTE_ADDR"))) eptr = "[addr?]"; if (!(hptr = CgiLibVarNull ("REMOTE_HOST"))) eptr = "[host?]"; if (Debug) fprintf (stdout, "|%s|%s|%s|%s|\n", sptr, aptr, hptr, eptr); if (!sptr) sptr = aptr; if (!sptr) sptr = hptr; if (sptr) { if (GeoIsAddress (sptr)) { setenv ("ADDR", aptr = sptr, 1); if (sptr == qptr) { /* address is from the query string, look up the host name */ sptr = GeoLocateLookup (NULL, sptr); if (*sptr == '[') setenv ("HOST", qptr, 1); else setenv ("HOST", sptr, 1); } else setenv ("HOST", hptr, 1); } else { setenv ("HOST", sptr, 1); if (sptr == qptr) { /* host name is from the query string, look up the address */ sptr = GeoLocateLookup (sptr, NULL); setenv ("ADDR", aptr = sptr, 1); } else setenv ("ADDR", aptr, 1); } } if (Debug) fprintf (stdout, "|%s|%s|%s|\n", aptr, hptr, eptr); sptr = GeoLocate (aservice, aptr); CgiLibResponseHeader (200, "text/plain", "Script-Control: X-content-encoding-gzip=0\n"); if (eptr) fprintf (stdout, "%s\n", eptr); else if (*sptr != '(') { cptr = sptr; while (*cptr && *cptr != GEOSEP) cptr++; if (*cptr) cptr++; for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; setenv ("COUNTRY", sptr, 1); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; setenv ("REGION", sptr, 1); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; setenv ("CITY", sptr, 1); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; setenv ("LAT", sptr, 1); *cptr++ = GEOSEP; } for (sptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; setenv ("LONG", sptr, 1); *cptr++ = GEOSEP; } sptr = buf; if (!strcmp (getenv("HOST"), getenv("ADDR"))) sptr += sprintf (sptr, "%s", getenv("ADDR")); else sptr += sprintf (sptr, "%s (%s)", getenv("HOST"), getenv("ADDR")); if (*(cptr = getenv("COUNTRY"))) sptr += sprintf (sptr, " %s", cptr); if (*(cptr = getenv("REGION"))) sptr += sprintf (sptr, " / %s", cptr); if (*(cptr = getenv("CITY"))) sptr += sprintf (sptr, " / %s", cptr); fprintf (stdout, "%s\n", buf); } else fprintf (stdout, "%s\n", sptr); if (!IsCgiPlus) break; Debug = 0; CgiLibCgiPlusEOF (); } exit (0); } #endif /* GEOLOCATE_OBJECT */ /*****************************************************************************/ /* Return geolocation data for |alookup| using |aservice| service. Errors are returned with a leading null character. */ char* GeoLocate (char* aservice, char* alookup) { static int ServiceIndex; static char LogValue [256], LookupError [64]; static char *agent = "WASD geolocate"; int idx; uint second; int64 ts64, tf64; short port; char *aptr, *sptr, *service, *lookup; char request [256], string [256]; struct in_addr ipaddr; struct hostent *heptr; /*********/ /* begin */ /*********/ if (aptr = GeoLocateResponse (NULL, alookup, NULL)) { /********************/ /* hit cached entry */ /********************/ GeoLocateMilliSecs = 0; return (aptr); } /***********/ /* service */ /***********/ sys$gettim (&ts64); lookup = alookup; service = aservice; if (service && service[0] == '*') { if (service = GeoServices[ServiceIndex++]) service++; else return (NULL); } while (!service) { if (LogValue[0] == '*') { if (service = GeoServices[ServiceIndex++]) service++; else ServiceIndex = 0; } else if (!LogValue[0]) service = GeoServices[0]; if (!service) GeoTrnLnm (GeoLocateLogicalName(NULL), LogValue, ServiceIndex); } /******************/ /* verify service */ /******************/ for (idx = 0; GeoServices[idx]; idx++) { for (sptr = GeoServices[idx]; !isalpha(*sptr); sptr++); if (!strcasecmp (service, sptr)) break; } if (!GeoServices[idx]) { GeoLocateFailCount++; return ("[GeoService?]"); } GeoLocateService = sptr; GeoServicePtr = GeoServices[idx]; if (!GeoIsAddress (lookup)) { /******************/ /* not IP address */ /******************/ aptr = GeoLocateLookup (lookup, NULL); if (*aptr == '[') { GeoLocateFailCount++; return (aptr); } lookup = aptr; } if (!strncmp (lookup, "127.0.0.1", 9) || !strncmp (lookup, "192.168.", 8) || !strncmp (lookup, "172.16.", 7) || !strncmp (lookup, "10.", 3)) return ("[private range]"); /*****************/ /* rate-limited? */ /*****************/ if (GeoIsNoDelay) { /* burst maximum GeoBurstLimit requests each GeoBurstPeriod seconds */ sys$gettim (&GeoCurrentTime64); if (GeoCurrentTime64 > GeoBurstTime64) { /* reset the limit every this many seconds */ GeoBurstTime64 = GeoCurrentTime64 + (TIME64_ONE_SEC * GeoBurstPeriod); GeoBurstCount = 0; } /* if that number of geolocations exceeded then */ if (GeoBurstCount++ > GeoBurstLimit) return ("(waiting...)"); } else if (GeoBurstTime64) { /* from ip-api.com rate limiting */ if (GeoCurrentTime64 > GeoBurstTime64) GeoBurstTime64 = 0; else return ("(waiting...)"); } if (GeoIsDelayed) { /* if that number of geolocations exceeded then */ if (GeoBurstCount++ > GeoBurstLimit) { sleep (GeoBurstPeriod); GeoBurstCount = 0; } } /****************/ /* HTTP request */ /****************/ sptr = request; if (!strcasecmp (service, "ip-api.com")) sptr += sprintf (sptr, "GET /json/%s", lookup); else if (!strcasecmp (service, "ipwhois.app")) sptr += sprintf (sptr, "GET /json/%s", lookup); else if (!strcasecmp (service, "www.geoplugin.net")) sptr += sprintf (sptr, "GET /json.gp?ip=%s", lookup); else if (!strcasecmp (service, "geolocation-db.com")) sptr += sprintf (sptr, "GET /jsonp/%s", lookup); else exit (SS$_BUGCHECK); sprintf (sptr, " HTTP/1.0\r\n\ Host: %s\r\n\ User-Agent: %s\n\ Connection: close\r\n\ \r\n", service, agent); if (Debug) fprintf (stdout, "|%s|\n|%s|\n", service, request); GeoLocateLookupCount++; aptr = GeoLocateRequest (GeoServicePtr, alookup, request); if (!aptr[0] && aptr[1]) GeoLocateFailCount++; /***************/ /* timekeeping */ /***************/ sys$gettim (&tf64); GeoLocateMilliSecs = (int)((tf64 - ts64) / (int64)10000); if (GeoLocateMilliSecs) { GeoTotalMilliSecs += GeoLocateMilliSecs; if (!GeoLocateMinMilliSecs) GeoLocateMinMilliSecs = GeoLocateMilliSecs; else if (GeoLocateMilliSecs < GeoLocateMinMilliSecs) GeoLocateMinMilliSecs = GeoLocateMilliSecs; if (GeoLocateMilliSecs > GeoLocateMaxMilliSecs) GeoLocateMaxMilliSecs = GeoLocateMilliSecs; if (GeoLocateLookupCount) GeoLocateAveMilliSecs = (int)(GeoTotalMilliSecs / GeoLocateLookupCount); } return (aptr); } /*****************************************************************************/ /* Connect to the |service| geolocation server and send an HTTP request. */ char* GeoLocateRequest (char* service, char* lookup, char* request) { static char response [2048]; int cnt, err, secure, sock; char *sptr; struct hostent *heptr; struct sockaddr_in serv_addr; struct in_addr ipaddr; #if GEOLOCATE_OPENSSL SSL_CTX *ctx; SSL *ssl; const SSL_METHOD *meth; X509 *server_cert; EVP_PKEY *pkey; #endif /* GEOLOCATE_OPENSSL */ /*********/ /* begin */ /*********/ if (secure = (*service == '+')) service++; ipaddr.s_addr = inet_addr (service); if (ipaddr.s_addr == INADDR_NONE) { heptr = gethostbyname(service); if (heptr == NULL) return ("[gethostbyname()]"); } sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); if (!sock) return ("[socket()]"); memset (&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; if (secure) #if GEOLOCATE_OPENSSL serv_addr.sin_port = htons(443); #else /* GEOLOCATE_OPENSSL */ return ("[OpenSSL not compiled option]"); #endif /* GEOLOCATE_OPENSSL */ else serv_addr.sin_port = htons(80); if (ipaddr.s_addr != INADDR_NONE) memcpy (&serv_addr.sin_addr.s_addr, &ipaddr.s_addr, sizeof(ipaddr.s_addr)); else memcpy (&serv_addr.sin_addr.s_addr, heptr->h_addr, heptr->h_length); err = connect (sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); if (err) return ("[connect()]"); #if GEOLOCATE_OPENSSL if (secure) { /***********/ /* TLS/SSL */ /***********/ SSL_library_init(); SSL_load_error_strings(); meth = TLS_client_method(); ctx = SSL_CTX_new(meth); if (!ctx) return ("[SSL_CTX_new()]"); ssl = SSL_new (ctx); if (!ssl) return ("[SSL_new()]"); SSL_set_fd (ssl, sock); err = SSL_connect(ssl); if (err) return ("[SSL_connect()]"); server_cert = SSL_get_peer_certificate (ssl); if (Debug) { printf ("SSL connection using %s\n", SSL_get_cipher (ssl)); if (server_cert != NULL) { fprintf (stdout, "server certificate:\n"); sptr = X509_NAME_oneline(X509_get_subject_name(server_cert),0,0); if (!sptr) return ("[X509_NAME_oneline()]"); fprintf (stdout, "\t subject: %s\n", sptr); free (sptr); sptr = X509_NAME_oneline(X509_get_issuer_name(server_cert),0,0); if (!sptr) return ("[X509_NAME_oneline()]"); fprintf (stdout, "\t issuer: %s\n", sptr); free(sptr); X509_free (server_cert); } else fprintf(stdout, "The SSL server does not have certificate.\n"); } err = SSL_write (ssl, request, strlen(request)); if (err) return ("[SSL_write()]"); response[0] = '\0'; err = SSL_read (ssl, response, sizeof(response)-1); if (err) return ("[SSL_read() response]"); response[err] = '\0'; if (Debug) fprintf (stdout, "received %d chars:'%s'\n", err, response); SSL_shutdown(ssl); err = SSL_shutdown(ssl); if (err) return ("[SSL_shutdown()]"); SSL_free(ssl); err = close(sock); if (err) return ("[close()]"); SSL_CTX_free(ctx); } else #endif /* GEOLOCATE_OPENSSL */ { /*************/ /* cleartext */ /*************/ err = write (sock, request, strlen(request)); if (!err) return ("[write()]"); response[0] = '\0'; err = read (sock, response, sizeof(response)-1); if (!err) return ("[read() response]"); response[err] = '\0'; if (Debug) fprintf (stdout, "received %d\n|%s|\n", err, response); err = close(sock); if (err) return ("[close()]"); } sptr = GeoLocateResponse (service, lookup, response); return (sptr); } /*****************************************************************************/ /* If |service| is NULL then check the cache for a |lookup| entry. Return if found. If |service| is non-NULL then parse the response according to the |service| scheme. Enter into the cache and return. If |response| is NULL then return a pointer to the full response for parsing. */ char* GeoLocateResponse (char* service, char* lookup, char* response) { static int CacheCount = 0, CacheMax = 0, NextCache = 0, PrevIdx = 0; static int HitIdx = -1; static char huh = '~'; static char msg [16]; /* lookup entry + ident entry + raw entry */ struct CacheEntry { char *lent; char *dent; char *rent; }; static struct CacheEntry *CacheData = NULL; int ch, chr, count, dlen, idx, llen, rlen, status, utf8; int IpApiXrl = 0, IpApiXttl = 0, WhoIsAddrCount = 0; int64 tf64, ts64; char *aptr, *cptr, *sptr, *zptr; char buf [256]; /*********/ /* begin */ /*********/ if (!service && !lookup && !response) { /*****************/ /* cache insight */ /*****************/ /* calculate the report space */ count = 0; for (idx = 0; idx < CacheCount; idx++) { cptr = CacheData[idx].rent; while (*cptr) cptr++; count += cptr - CacheData[idx].lent + 32; } aptr = sptr = calloc (1, count + 64); /* populate the report as JSON entries */ sptr += sprintf (sptr, "{\"$data\":\"geolocate\",\"CacheCount\":%d,\"records\":[", CacheCount); for (idx = 0; idx < CacheCount; idx++) { sptr += sprintf (sptr, "{\"idx%d\":\"%s$%s$", idx, CacheData[idx].lent, CacheData[idx].dent); for (cptr = CacheData[idx].rent; *cptr; cptr++) if (*cptr == '\"') *sptr++ = '\''; else *sptr++ = *cptr; for (cptr = "\"},"; *cptr; *sptr++ = *cptr++); } if (*(sptr-1) == ',') sptr--; /* trailing comma */ sptr += sprintf (sptr, "]}"); return (aptr); } if (!lookup) { /*******************/ /* reset the cache */ /*******************/ for (idx = 0; idx < CacheMax; idx++) { if (CacheData[idx].lent) free (CacheData[idx].lent); CacheData[idx].lent = CacheData[idx].dent = CacheData[idx].rent = NULL; } return (NULL); } if (!service) { /*****************/ /* look in cache */ /*****************/ if (!CacheData) return (NULL); for (idx = PrevIdx; idx < CacheCount; idx++) { cptr = lookup; sptr = CacheData[idx].lent; while (*cptr && *sptr && tolower(*cptr) == tolower(*sptr)) { cptr++; sptr++; } if (!*cptr && !*sptr) { /*******/ /* hit */ /*******/ HitIdx = PrevIdx = idx; GeoLocateHitCount++; return (CacheData[idx].dent); } if (PrevIdx) idx = PrevIdx = 0; } /********/ /* miss */ /********/ HitIdx = -1; idx = PrevIdx = 0; return (NULL); } if (!response) { /****************/ /* response hit */ /****************/ if (HitIdx < 0) return (NULL); return (CacheData[HitIdx].rent); } /***************/ /* HTTP header */ /***************/ for (aptr = response; *aptr && *aptr != ' '; aptr++); if (*aptr == ' ') status = atoi(aptr+1); else status = 0; if (status != 200) { sprintf (msg, "[HTTP:%d]", status); return (msg); } for (aptr = response; *aptr && *(ushort*)aptr != '\n\n' && *(ulong*)aptr != '\r\n\r\n'; aptr++); if (*(ushort*)aptr == '\n\n') { *aptr = '\0'; aptr += 2; } else if (*(ulong*)aptr == '\r\n\r\n') { *aptr = '\0'; aptr += 4; } else return (NULL); utf8 = 0; if (strstr (response, "charset=utf-8")) utf8 = 1; if (!strcasecmp (service, "ip-api.com")) { if (cptr = strstr (response, "X-Rl: ")) IpApiXrl = atoi(cptr+6); if (cptr = strstr (response, "X-Ttl: ")) IpApiXttl = atoi(cptr+7); } response = aptr; if (Debug) fprintf (stdout, "UTF-8 %d\n|%s|\n", utf8, response); /***********************/ /* cache HTTP response */ /***********************/ if (!CacheMax) { /* allocate an array of pointers to cache entries */ CacheData = calloc (CacheMax = GeoLocateCacheMax(0), sizeof(struct CacheEntry)); if (!CacheData) exit (vaxc$errno); } if ((idx = NextCache++) >= CacheMax) { /* round-robin allocation */ idx = NextCache = 0; } /* leave space for multiple GEOSEP and cache stats */ zptr = (sptr = buf) + sizeof(buf)-32; for (cptr = lookup; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; llen = sptr - buf; if (utf8) if (!GeoIsUtf8) GeoLocateMungeUtf8 (response); GeoLocateMungeUniEsc (response); if (!strcasecmp (service, "ip-api.com")) { /**************/ /* ip-api.com */ /**************/ *sptr++ = GEOSEP; if (aptr = strstr (response, "\"status\":\"")) { if (!strncmp (aptr+10, "fail", 4)) { if (aptr = strstr (response, "\"message\":\"")) { for (cptr = (aptr += 11); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } } } if (aptr = strstr (response, "\"country\":\"")) { for (cptr = (aptr += 11); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"regionName\":\"")) { for (cptr = (aptr += 14); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"city\":\"")) { for (cptr = (aptr += 8); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"lat\":")) { for (cptr = (aptr += 6); *cptr && *cptr != ',' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"lon\":")) { for (cptr = (aptr += 6); *cptr && *cptr != ',' && sptr < zptr; *sptr++ = *cptr++); } } else if (!strcasecmp (service, "ipwhois.app")) { /***************/ /* ipwhois.app */ /***************/ *sptr++ = GEOSEP; if (aptr = strstr (response, "\"success\":")) { if (!strncmp (aptr+10, "false", 4)) { if (aptr = strstr (response, "\"message\":\"")) { for (cptr = (aptr += 11); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } } } if (aptr = strstr (response, "\"country\":\"")) { for (cptr = (aptr += 11); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"region\":\"")) { for (cptr = (aptr += 10); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"city\":\"")) { for (cptr = (aptr += 8); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"latitude\":")) { for (cptr = (aptr += 11); *cptr && *cptr != ',' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"longitude\":")) { for (cptr = (aptr += 12); *cptr && *cptr != ',' && sptr < zptr; *sptr++ = *cptr++); } if (aptr = strstr (response, "\"completed_requests\":")) WhoIsAddrCount = atoi(aptr + 21); } else if (!strcasecmp (service, "www.geoplugin.net")) { /*********************/ /* www.geoplugin.net */ /*********************/ *sptr++ = GEOSEP; if (aptr = strstr (response, "\"geoplugin_status\":")) { if (strncmp (aptr+19, "200", 3)) { for (cptr = "status:"; *cptr; *sptr++ = *cptr++); *sptr++ = aptr[19]; *sptr++ = aptr[20]; *sptr++ = aptr[21]; } } if (aptr = strstr (response, "\"geoplugin_countryName\":\"")) { for (cptr = (aptr += 25); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"geoplugin_regionName\":\"")) { for (cptr = (aptr += 24); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"geoplugin_city\":\"")) { for (cptr = (aptr += 18); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"geoplugin_latitude\":\"")) { for (cptr = (aptr += 22); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"geoplugin_longitude\":\"")) { for (cptr = (aptr += 23); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } if (aptr = strstr (response, "\"geoplugin_longitude\":\"")) { for (cptr = (aptr += 23); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } } else if (!strcasecmp (service, "geolocation-db.com")) { /**********************/ /* geolocation-db.com */ /**********************/ *sptr++ = GEOSEP; if (aptr = strstr (response, "\"country_name\":\"")) { for (cptr = (aptr += 16); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"state\":\"")) { for (cptr = (aptr += 9); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"city\":\"")) { for (cptr = (aptr += 8); *cptr && *cptr != '\"' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"latitude\":")) { for (cptr = (aptr += 11); *cptr && *cptr != ',' && sptr < zptr; *sptr++ = *cptr++); } *sptr++ = GEOSEP; if (aptr = strstr (response, "\"longitude\":")) { for (cptr = (aptr += 12); *cptr && *cptr != ',' && sptr < zptr; *sptr++ = *cptr++); } } else { /* empty response */ *sptr++ = GEOSEP; *sptr++ = GEOSEP; *sptr++ = GEOSEP; *sptr++ = GEOSEP; *sptr++ = GEOSEP; } *sptr = '\0'; /******************/ /* additonal data */ /******************/ if (WhoIsAddrCount) sptr += sprintf (sptr, "%c%d/%d/%d+", GEOSEP, idx, NextCache, WhoIsAddrCount); else sptr += sprintf (sptr, "%c%d/%d+", GEOSEP, idx, NextCache); if (IpApiXrl <= 12 && IpApiXttl) { /****************************/ /* ip-api.com rate limiting */ /****************************/ /* do not make another request until 2 x period */ if (GeoIsNoDelay) { sys$gettim (&GeoCurrentTime64); GeoBurstTime64 = GeoCurrentTime64 + (TIME64_ONE_SEC * GeoBurstPeriod * 2); } else { /* rate limit must be enforced */ sleep (GeoBurstPeriod * 2); } } /**************/ /* into cache */ /**************/ /* quick sanity check */ if (sptr - buf >= sizeof(buf)-1) exit (SS$_BUGCHECK); dlen = sptr - buf; /* free any previous cache entry */ if (CacheData[idx].lent) { free (CacheData[idx].lent); CacheData[idx].lent = CacheData[idx].dent = CacheData[idx].rent = NULL; } else CacheCount++; for (cptr = response; *cptr; *cptr++); rlen = cptr - response; CacheData[idx].lent = calloc (1, llen + dlen + rlen + 3); if (!CacheData[idx].lent) exit (vaxc$errno); /* three consecutive null-terminated strings */ sptr = CacheData[idx].lent; for (cptr = lookup; *cptr; *sptr++ = *cptr++); *sptr++ = '\0'; CacheData[idx].dent = sptr; for (cptr = buf; *cptr; *sptr++ = *cptr++); *sptr++ = '\0'; CacheData[idx].rent = sptr; for (cptr = response; *cptr; *sptr++ = *cptr++); *sptr = '\0'; if (Debug) fprintf (stdout, "%d\n|%s|\n|%s|\n|%s|\n", llen + dlen + rlen + 3, CacheData[idx].lent, CacheData[idx].dent, CacheData[idx].rent); return (CacheData[HitIdx = idx].dent); } /****************************************************************************/ /* */ char* GeoLocateData (char* geoptr, int extend) { static char buf [128+32]; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoLocateData()\n"); if (!(cptr = geoptr) || !*cptr) return ("[bugcheck]"); zptr = (sptr = buf) + sizeof(buf)-32; if (cptr && *cptr == '[') { /* error message */ while (*cptr && sptr < zptr) *sptr++ = *cptr++; } else if (cptr) { /* span over IP address */ while (*cptr && *cptr != GEOSEP) cptr++; if (*cptr) cptr++; /* country */ while (*cptr && *cptr != GEOSEP) *sptr++ = *cptr++; if (*cptr) cptr++; /* region */ if (*cptr != GEOSEP) { *sptr++ = ' '; *sptr++ = '/'; *sptr++ = ' '; } while (*cptr && *cptr != GEOSEP && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; /* city */ if (*cptr != GEOSEP) { *sptr++ = ' '; *sptr++ = '/'; *sptr++ = ' '; } while (*cptr && *cptr != GEOSEP && sptr < zptr) *sptr++ = *cptr++; if (extend) { if (*cptr) cptr++; /* latitude */ if (*cptr != GEOSEP) { *sptr++ = ' '; *sptr++ = '/'; *sptr++ = ' '; } while (*cptr && *cptr != GEOSEP && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; /* longitude */ if (*cptr != GEOSEP) { *sptr++ = ','; *sptr++ = ' '; } while (*cptr && *cptr != GEOSEP && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; /* cache stats */ *sptr++ = ','; *sptr++ = ' '; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } } *sptr = '\0'; return (buf); } /****************************************************************************/ /* Parse the full JSON response one field at a time returning a pointer to a buffer containing "\0\0" and NULL when fully parsed. */ char* GeoLocateParse (char **response) { static char *aptr = NULL; int len; char *cptr, *sptr, *zptr; char buf [512]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoResponseParse()\n"); if (aptr) free (aptr); if (!*response) return (NULL); cptr = *response; zptr = (sptr = buf) + sizeof(buf)-1; while (*cptr && *cptr != '\"') cptr++; if (*cptr) cptr++; while (*cptr && *cptr != '\"' && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } *sptr++ = '\0'; len = sptr - buf; if (Debug) fprintf (stdout, "|%s|\n", buf); while (*cptr && *cptr != '\"') cptr++; if (*cptr == '\"') cptr++; while (*cptr && *cptr != ':') cptr++; if (*cptr) cptr++; while (*cptr && isspace(*cptr)) cptr++; if (*cptr == '\"') { cptr++; while (*cptr && *cptr != '\"' && sptr < zptr) { if (*cptr == '\\' && *(cptr+1)) cptr++; *sptr++ = *cptr++; } while (*cptr && *cptr != '\"') cptr++; if (*cptr == '\"') cptr++; } else { while (*cptr && *cptr != ',' && !isspace(*cptr) && sptr < zptr) *sptr++ = *cptr++; while (*cptr && *cptr != ',' && !isspace(*cptr)) cptr++; } *sptr++ = '\0'; if (Debug) fprintf (stdout, "|%s|\n", buf + len); len = sptr - buf; while (*cptr && isspace(*cptr)) cptr++; if (*cptr == ',') cptr++; while (*cptr && isspace(*cptr)) cptr++; if (*cptr == '}') *response = NULL; else *response = cptr; aptr = calloc (1, len); if (!aptr) exit (vaxc$errno); zptr = (sptr = aptr) + len; for (cptr = buf; sptr < zptr; *sptr++ = *cptr++); return (aptr); } /*****************************************************************************/ /* If |name| is non-NULL lookup the IP address using the host name. If |addr| is non-NULL lookup the host name using the address. */ char* GeoLocateLookup ( char *name, char *addr ) { static char buf [256]; int retry, retval; char *cptr, *sptr, *zptr; void *addptr; struct sockaddr_in addr4; struct sockaddr_in6 addr6; struct addrinfo hints; struct addrinfo *aiptr, *resaiptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoLocateLookup() |%s|%s|\n", name, addr); if (addr) { retval = 0; memset (&addr4, 0, sizeof(addr4)); if (inet_pton (AF_INET, addr, &addr4.sin_addr) > 0) { /* MultiNet does not support BSD 4.4 AF_INET addresses */ #ifdef NO_SOCKADDR_LEN_4 /* this kludge seems to work for both! */ *(__unaligned short*)&addr4 = AF_INET; #else addr4.sin_len = sizeof(struct sockaddr_in); addr4.sin_family = AF_INET; #endif for (retry = LOOKUP_RETRY; retry; retry--) { retval = getnameinfo ((struct sockaddr*)&addr4, sizeof(addr4), buf, sizeof(buf), NULL, 0, NI_NAMEREQD); if (retval != EINTR && retval != EAI_AGAIN) break; sleep (1); } if (retval) { if (Debug) fprintf (stdout, "a) %d\n", retval); if (retval == EAI_NONAME) strcpy (buf, "[unresolved]"); else if (retval == EAI_FAIL || retval == EAI_AGAIN) strcpy (buf, "[failed]"); else if (retval == EAI_SYSTEM) sprintf (buf, "[%s %d]", strerror(errno), errno); else sprintf (buf, "[%s %d]", gai_strerror(retval), retval); } return (buf); } else { memset (&addr6, 0, sizeof(addr6)); if (inet_pton (AF_INET6, addr, &addr6.sin6_addr) > 0) { addr6.sin6_len = sizeof(struct sockaddr_in6); addr6.sin6_family = AF_INET6; for (retry = LOOKUP_RETRY; retry; retry--) { retval = getnameinfo ((struct sockaddr*)&addr6, addr6.sin6_len, buf, sizeof(buf), NULL, 0, NI_NAMEREQD); if (retval != EINTR && retval != EAI_AGAIN) break; sleep (1); } if (retval) { if (Debug) fprintf (stdout, "b) %d\n", retval); if (retval == EAI_NONAME) strcpy (buf, "[unresolved]"); else if (retval == EAI_FAIL || retval == EAI_AGAIN) strcpy (buf, "[failed]"); else if (retval == EAI_SYSTEM) sprintf (buf, "[%s %d]", strerror(errno), errno); else sprintf (buf, "[%s %d]", gai_strerror(retval), retval); } } return (buf); } } if (name) { aiptr = NULL; memset (&hints, 0, sizeof(hints)); hints.ai_flags |= AI_CANONNAME; retval = 0; for (retry = LOOKUP_RETRY; retry; retry--) { retval = getaddrinfo (name, NULL, &hints, &resaiptr); if (retval != EINTR && retval != EAI_AGAIN) break; sleep (1); } if (retval) { if (Debug) fprintf (stdout, "c) %d\n", retval); if (retval == EAI_NONAME) strcpy (buf, "[unresolved]"); else if (retval == EAI_FAIL || retval == EAI_AGAIN) strcpy (buf, "[failed]"); else if (retval == EAI_SYSTEM) sprintf (buf, "[%s %d]", strerror(errno), errno); else sprintf (buf, "[%s %d]", gai_strerror(retval), retval); return (buf); } else { /* potentially multiple addresses for the one host name */ zptr = (sptr = buf) + sizeof(buf)-8; /* first IPv4 */ for (aiptr = resaiptr; aiptr; aiptr = aiptr->ai_next) { addptr = &((struct sockaddr_in *)aiptr->ai_addr)->sin_addr; if (aiptr->ai_family == AF_INET) break; } if (!aiptr) { /* then IPv6 */ for (aiptr = resaiptr; aiptr; aiptr = aiptr->ai_next) { addptr = &((struct sockaddr_in6 *)aiptr->ai_addr)->sin6_addr; if (aiptr->ai_family != AF_INET6) break; } } if (!aiptr) { strcpy (buf, "[not AF_INET or AF_INET6]"); return (buf); } if (sptr > buf) *sptr++ = ' '; if (!inet_ntop (aiptr->ai_family, addptr, sptr, zptr - sptr)) sprintf (buf, "[%s %d]", strerror(errno), errno); return (buf); } /* free the addrinfo */ freeaddrinfo(aiptr); } return ("[bugcheck]"); } /*****************************************************************************/ /* UTF-8 null string is munged into an obvious character. */ static void GeoLocateMungeUtf8 (char* buf) { char ch, chr; char *aptr, *cptr, *sptr; if (Debug) fprintf (stdout, "GeoLocateMungeUtf8() |%s|\n", buf); /* rude and crude */ if (Debug) fprintf (stdout, "b1 |%s|\n", buf); aptr = sptr = buf; while (*aptr) { if (*aptr & 0x80) { /* unicode point */ aptr++; *sptr++ = *GeoSubsChar; if ((*aptr & 0xF0) < 0xE0) aptr++; else if ((*aptr & 0xF0) == 0xE0) aptr += 2; else aptr += 3; } else *sptr++ = *aptr++; } *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|\n", buf); } /*****************************************************************************/ /* Unicode escape (\u0000) null string is munged into obvious character. */ static void GeoLocateMungeUniEsc (char *buf) { char ch, chr; char *aptr, *sptr; if (Debug) fprintf (stdout, "GeoLocateMungeUniEsc() |%s|\n", buf); aptr = sptr = buf; while (*aptr) { /* e.g. "\u00nn" */ if (*aptr != '\\') { *sptr++ = *aptr++; continue; } if (*(aptr+1) != 'u') { *sptr++ = *aptr++; *sptr++ = *aptr++; continue; } if (*(aptr+2) != '0' || *(aptr+3) != '0') { *sptr++ = *GeoSubsChar; if (*aptr) aptr++; if (*aptr) aptr++; if (*aptr) aptr++; if (*aptr) aptr++; if (*aptr) aptr++; if (*aptr) aptr++; continue; } ch = -1; aptr += 4; /* first hex digit */ if (chr = *aptr) aptr++; if (chr >= '0' && chr <= '9') ch = (chr - '0') << 4; else if (chr >= 'A' && chr <= 'F') ch = (chr - 'A') << 4; else if (chr >= 'a' && chr <= 'f') ch = (chr - 'a') << 4; else ch = -1; if (ch > 0) { /* second hex digit */ if (chr = *aptr) aptr++; if (chr >= '0' && chr <= '9') ch += chr - '0'; else if (chr >= 'A' && chr <= 'F') ch += chr - 'A'; else if (chr >= 'a' && chr <= 'f') ch += chr - 'a'; else ch = -1; } else if (*aptr) aptr++; /* don't want null or DEL characters */ if (ch > 0 && ch < 127) *(unsigned char*)sptr++ = ch; else *(unsigned char*)sptr++ = *GeoSubsChar; } *sptr = '\0'; if (Debug) fprintf (stdout, "|%s|\n", buf); } /*****************************************************************************/ /* By default the behaviour is non-bursty. If busty then call GeoLocateNoDelay(1) before processing. */ void GeoLocateNoDelay (int enable) { GeoIsNoDelay = enable; GeoIsDelayed = !enable; } /*****************************************************************************/ /* By default the behaviour is delayed. Every call must return a result. If calls can be missed then call GeoLocateDelayed(0) before processing. */ void GeoLocateDelayed (int enable) { GeoIsDelayed = enable; } /*****************************************************************************/ /* By default UTF-8 is (partially) munged into 8 bit ASCII. If this is not required call this GeoLocateUtf8(1) before processing. */ void GeoLocateUtf8 (int enable) { GeoIsUtf8 = enable; } /*****************************************************************************/ /* If |max| is zero return the CacheMax setting. If the first call is non-zero set that as CacheMax. */ int GeoLocateCacheMax (int max) { static int CacheMax = 0; /*********/ /* begin */ /*********/ if (!max && !CacheMax) return (CacheMax = GEOLOCATE_CACHE_MAX); if (max && !CacheMax) { if (max < 64) max = 64; if (max > 4096) max = 4096; return (CacheMax = max); } return (CacheMax); } /*****************************************************************************/ /* If |logname| is non-NULL store the name and translate and return any value. If NULL then return the previously set logical name. */ char* GeoLocateLogicalName (char *logname) { static char LogicalName [64] = "GEOLOCATE_SERVICE"; char *cptr, *sptr, *zptr; if (!logname) return (LogicalName); zptr = (sptr = LogicalName) + sizeof(LogicalName)-1; for (cptr = logname; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; return (GeoTrnLnm (LogicalName, NULL, 0)); } /*****************************************************************************/ /* */ char* GeoLocateSoftwareId () { return ((char*)SoftwareId); } /*****************************************************************************/ /* Return true if it looks like an IPv4 or IPv6 address. */ int GeoIsAddress (char *string) { char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoIsAddress() |%s|\n", string); if (!string) return (0); for (cptr = string; *cptr && isspace(*cptr); cptr++); if (!*cptr) return (0); cptr = string; if (*(unsigned long*)cptr == '::FF' && !memcmp (cptr, "::FFFF:", 7)) cptr += 7; else if (*(unsigned long*)cptr == '::ff' && !memcmp (cptr, "::ffff:", 7)) cptr += 7; while (*cptr && (isdigit(*cptr) || *cptr == '.')) cptr++; if (!*cptr) return (1); for (cptr = string; *cptr && (isxdigit(*cptr) || *cptr == ':' || *cptr == '-'); cptr++); if (!*cptr) return (1); return (0); } /*****************************************************************************/ /* Translate a logical name using LNM$FILE_DEV. Returns a pointer to the value string, or NULL if the name does not exist. If 'LogValue' is supplied the logical name is translated into that (assumed to be large enough), otherwise it's translated into an internal static buffer. 'IndexValue' should be zero for a 'flat' logical name, or 0..127 for interative translations. */ static char* GeoTrnLnm ( char *LogName, char *LogValue, int IndexValue ) { static unsigned short ValueLength; static unsigned long LnmAttributes, LnmIndex; static char StaticLogValue [256]; static $DESCRIPTOR (LogNameDsc, ""); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } LnmItems [] = { { sizeof(LnmIndex), LNM$_INDEX, &LnmIndex, 0 }, { sizeof(LnmAttributes), LNM$_ATTRIBUTES, &LnmAttributes, 0 }, { 255, LNM$_STRING, 0, &ValueLength }, { 0,0,0,0 } }; int status; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoTrnLnm() |%s| %d\n", LogName, IndexValue); LnmIndex = IndexValue; LogNameDsc.dsc$a_pointer = LogName; LogNameDsc.dsc$w_length = strlen(LogName); if (LogValue) cptr = LnmItems[2].buf_addr = LogValue; else cptr = LnmItems[2].buf_addr = StaticLogValue; status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems); if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status); if (!(status & 1) || !(LnmAttributes & LNM$M_EXISTS)) { if (Debug) fprintf (stdout, "|(null)|\n"); return (NULL); } cptr[ValueLength] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", cptr); return (cptr); } /****************************************************************************/ /* Parse the full JSON response into symbols GEOLOCATE__.. */ #if GEOLOCATE_SYMBOL void GeoResponseSymbols () { char *aptr, *cptr, *sptr, *xptr, *zptr; char buf [256]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoResponseSymbols()\n"); xptr = GeoLocateResponse ("", "", NULL); while (aptr = GeoLocateParse (&xptr)) { zptr = (sptr = buf) + sizeof(buf)-1; for (cptr = "GEOLOCATE__"; *cptr; *sptr++ = *cptr++); for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (*(cptr+1)) cptr++; GeoSetSymbol (buf, cptr, 0); } GeoSetSymbol ("GEOLOCATE__VERSION", SOFTWAREID + sizeof(SOFTWARENM), 0); } #endif /* GEOLOCATE_SYMBOL */ /****************************************************************************/ /* Assign a global symbol. If the string pointer is null the numeric value is used. Symbol lengths are fixed to a maximum of 255. */ #if GEOLOCATE_SYMBOL void GeoSetSymbol ( char *Name, char *String, int Value ) { static char ValueString [32]; static $DESCRIPTOR (NameDsc, ""); static $DESCRIPTOR (ValueDsc, ""); static $DESCRIPTOR (ValueFaoDsc, "!UL"); static $DESCRIPTOR (ValueStringDsc, ValueString); int status; int CliSymbolType = GeoSymbolScope; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoSetSymbol() |%s|%s| %d\n", Name, String, Value); NameDsc.dsc$a_pointer = Name; NameDsc.dsc$w_length = strlen(Name); if (!String) { ValueDsc.dsc$a_pointer = ValueString; sys$fao (&ValueFaoDsc, &ValueDsc.dsc$w_length, &ValueStringDsc, Value); ValueString[ValueDsc.dsc$w_length] = '\0'; } else { ValueDsc.dsc$a_pointer = String; if ((ValueDsc.dsc$w_length = strlen(String)) > 255) ValueDsc.dsc$w_length = 255; } if (Debug) fprintf (stdout, "|%s| %d\n", Name, ValueDsc.dsc$w_length); status = lib$set_symbol (&NameDsc, &ValueDsc, &CliSymbolType); if (!(status & 1)) exit (status); } #endif /* GEOLOCATE_SYMBOL */ /*****************************************************************************/