/*****************************************************************************/ /* 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." GeoLocate employs a simple algorithm against the header fields X-Rl and X-Ttl. When X-Rl reaches 12 (requests to go) a 5 second delay (sleep) is introduced, providing a maximum 12 * 5 = 60 seconds which should effectively prevent any 429 response(s). 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" 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. BUILD DETAILS ------------- See BUILD_GEOLOCATE.COM procedure. VEGEOSEPION LOG ----------- 26-FEB-2022 MGD initial */ /*****************************************************************************/ #define SOFTWAREVN "1.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; #endif static char *GeoServices [] = { "ip-api.com", "ipwhois.app", "www.geoplugin.net", "geolocation-db.com", NULL }; static int GeoSleptMilliSecs, GeoSymbolScope = LIB$K_CLI_GLOBAL_SYM; static int64 GeoTotalMilliSecs; static char* GeoServicePtr; static int GeoIsAddress (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 int main (int argc, char *argv[]) { int cnt, idx, loop, full, out; char *aservice, *alookup, *aptr, *cptr; /*********/ /* begin */ /*********/ Debug = (GeoTrnLnm ("GEOLOCATE_DEBUG", NULL, 0) != NULL); 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 (!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 (!strcasecmp (argv[1], "/version")) fprintf (stdout, "%s\n", GeoLocateSoftwareId()); if (!alookup) return (SS$_INVARG); for (cnt = 1; cnt <= loop; cnt++) { while (aptr = 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 (aptr && *aptr == '[') { GeoSetSymbol ("GEOLOCATE_ERROR", aptr, 0); if (out) fprintf (stdout, "*** %s ***\n", aptr); } else { if (out) fprintf (stdout, "|%s| %dms lookup:%d hit:%d\n", aptr, GeoLocateMilliSecs, GeoLocateLookupCount, GeoLocateHitCount); cptr = aptr; while (*cptr && *cptr != GEOSEP) cptr++; if (*cptr) cptr++; for (aptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_COUNTRY", aptr, 0); *cptr++ = GEOSEP; } for (aptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_REGION", aptr, 0); *cptr++ = GEOSEP; } for (aptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_CITY", aptr, 0); *cptr++ = GEOSEP; } for (aptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_LAT", aptr, 0); *cptr++ = GEOSEP; } for (aptr = cptr; *cptr && *cptr != GEOSEP; cptr++); if (*cptr) { *cptr = '\0'; GeoSetSymbol ("GEOLOCATE_LONG", aptr, 0); *cptr++ = GEOSEP; } } if (full) GeoResponseSymbols (); if (!aservice || *aservice != '*') break; GeoLocateResponse (NULL, NULL, NULL); } } } #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; int64 ts64, tf64; short port; char *aptr, *sptr, *service, *lookup; char request [256], string [256]; struct in_addr ipaddr; struct hostent *heptr; /*********/ /* begin */ /*********/ 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 ("\0GeoService?"); } GeoLocateService = sptr; GeoServicePtr = GeoServices[idx]; if (!GeoIsAddress (lookup)) { /******************/ /* not IP address */ /******************/ aptr = GeoLocateLookup (lookup, NULL); if (*aptr == '[') { GeoLocateFailCount++; return (aptr); } lookup = aptr; } if (aptr = GeoLocateResponse (NULL, lookup, NULL)) { /********************/ /* hit cached entry */ /********************/ GeoLocateMilliSecs = GeoSleptMilliSecs = 0; return (aptr); } /****************/ /* 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, lookup, request); if (!aptr[0] && aptr[1]) GeoLocateFailCount++; /***************/ /* timekeeping */ /***************/ sys$gettim (&tf64); GeoLocateMilliSecs = (int)((tf64 - ts64) / (int64)10000); if (GeoSleptMilliSecs < GeoLocateMilliSecs) GeoLocateMilliSecs -= GeoSleptMilliSecs; 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 ("\0gethostbyname()"); } sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); if (!sock) return ("\0socket()"); 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 ("\0OpenSSL 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 ("\0connect()"); #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 ("\0SSL_CTX_new()"); ssl = SSL_new (ctx); if (!ssl) return ("\0SSL_new()"); SSL_set_fd (ssl, sock); err = SSL_connect(ssl); if (err) return ("\0SSL_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 ("\0X509_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 ("\0X509_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 ("\0SSL_write()"); response[0] = '\0'; err = SSL_read (ssl, response, sizeof(response)-1); if (err) return ("[SSL_read()]"); response[err] = '\0'; if (Debug) fprintf (stdout, "received %d chars:'%s'\n", err, response); SSL_shutdown(ssl); err = SSL_shutdown(ssl); if (err) return ("\0SSL_shutdown()"); SSL_free(ssl); err = close(sock); if (err) return ("\0close()"); SSL_CTX_free(ctx); } else #endif /* GEOLOCATE_OPENSSL */ { /*************/ /* cleartext */ /*************/ err = write (sock, request, strlen(request)); if (!err) return ("\0write()"); response[0] = '\0'; err = read (sock, response, sizeof(response)-1); if (!err) return ("[read()]"); response[err] = '\0'; if (Debug) fprintf (stdout, "received %d\n|%s|\n", err, response); err = close(sock); if (err) return ("\0close()"); } 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 msg [16]; struct CacheEntry { char *dent; char *rent; }; static struct CacheEntry *CacheData = NULL; int ch, clen, count, idx, rlen, status, utf8; int IpApiXrl = 0, IpApiXttl = 0, WhoIsAddrCount = 0; int64 tf64, ts64; char *aptr, *cptr, *sptr, *zptr; char buf [256]; /*********/ /* begin */ /*********/ if (!lookup && !service && !response) { if (HitIdx >= 0) return CacheData[HitIdx].dent; return (NULL); } if (!lookup) { /* special case; reset the cache */ for (idx = 0; idx < CacheMax; idx++) { if (CacheData[idx].dent) free (CacheData[idx].dent); CacheData[idx].dent = CacheData[idx].rent = NULL; } return (NULL); } if (!service) { /*****************/ /* look in cache */ /*****************/ if (!CacheData) return (NULL); for (idx = PrevIdx; idx < CacheCount; idx++) { /* assumes at least class C CIDR and excludes final IPv4 octet */ cptr = lookup; sptr = CacheData[idx].dent; while (*cptr && *sptr != GEOSEP && tolower(*cptr) == tolower(*sptr)) { cptr++; sptr++; } if ((*cptr == '.' || *cptr == ':') && *sptr == GEOSEP) { /*******/ /* hit */ /*******/ HitIdx = PrevIdx = idx; GeoLocateHitCount++; /* purge any potential ip-api.com rate limit data */ while (*sptr && *sptr != '$') sptr++; if (*sptr && isdigit(*(sptr+1))) *sptr = '\0'; /* along with any lookup indicator */ if (*(sptr-1) == '+') *(sptr-1) = '\0'; 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) { CacheData = calloc (CacheMax = GeoLocateCacheMax(0), sizeof(struct CacheEntry)); if (!CacheData) exit (vaxc$errno); } if ((idx = NextCache++) >= CacheMax) { /* round-robin allocation */ idx = NextCache = 0; } count = 0; if (CacheData[idx].dent) { if (*(cptr = CacheData[idx].dent)) { while (*cptr) cptr++; while (*cptr != GEOSEP && *cptr != '/') cptr--; if (*cptr) cptr++; count = atoi(cptr); } } /* leave space for multiple GEOSEP and cache stats */ zptr = (sptr = buf) + sizeof(buf)-32; for (cptr = lookup; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* if IPv4 assume at least class C CIDR and excise final octet */ while (sptr > buf && *sptr != '.' && *sptr != ':') sptr--; if (*sptr == ':') { /* IPv6 ignore the interface 64 bits, e.g. 2604:a880:400:d0~::efe:e00 */ for (sptr = buf; *sptr && *sptr != ':'; sptr++); for (*sptr == ':' ? sptr++ : sptr; *sptr && *sptr != ':'; sptr++); for (*sptr == ':' ? sptr++ : sptr; *sptr && *sptr != ':'; sptr++); for (*sptr == ':' ? sptr++ : sptr; *sptr && *sptr != ':'; sptr++); } 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'; if (GeoLocateUtf8 (0)) { /*****************/ /* convert UTF-8 */ /*****************/ if (utf8) { /* rude and crude */ for (aptr = buf; *aptr; aptr++) { /* https://codebeautify.org/utf8-converter */ if ((*aptr & 0xe0) == 0xc0) { /* UTF-8 sequence */ if (!(*(aptr+1) & 0x1c)) { /* latin-1 ASCII 128 to 255 */ ch = (*(aptr+1) & 0x03) << 6; *aptr = ch; for (sptr = aptr + 1; *(sptr+1); *sptr = *(sptr+1)) *sptr++; } } } } aptr = buf; while (cptr = strstr (aptr, "\\u00")) { if (*(cptr+4) && *(cptr+5)) { if (sscanf (cptr+4, "%2x", &ch) == 1) { /* not if it is a null character */ if (ch) { *(unsigned char*)(aptr = cptr) = ch; for (sptr = (cptr++ + 6); *cptr; *cptr++ = *sptr++); *cptr = '\0'; } } } for (sptr = aptr++; *sptr; sptr++); } } /******************/ /* additonal data */ /******************/ if (WhoIsAddrCount) sptr += sprintf (sptr, "%c%d/%d/%d+", GEOSEP, idx, ++count, WhoIsAddrCount); else sptr += sprintf (sptr, "%c%d/%d+", GEOSEP, idx, ++count); if (CacheCount < CacheMax) CacheCount++; if (IpApiXrl <= 12 && IpApiXttl) { /****************************/ /* ip-api.com rate limiting */ /****************************/ count = 5; /* seconds */ sptr += sprintf (sptr, "$%d/%d/%d", IpApiXrl, IpApiXttl, count); if (Debug) fprintf (stdout, "ip-api.com sleeping %d\n", count); fflush(stdout); sys$gettim (&ts64); sleep (count); sys$gettim (&tf64); GeoSleptMilliSecs = (int)((tf64 - ts64) / (int64)10000); } /**************/ /* into cache */ /**************/ /* quick sanity check */ if (sptr - buf >= sizeof(buf)-1) exit (SS$_BUGCHECK); /* free any previous cache entry */ if (CacheData[idx].dent) free (CacheData[idx].dent); clen = sptr - buf; for (cptr = response; *cptr; *cptr++); rlen = cptr - response; CacheData[idx].dent = calloc (1, clen + rlen + 2); if (!CacheData[idx].dent) exit (vaxc$errno); /* two consecutive null-terminated strings */ sptr = CacheData[idx].dent; 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", clen + rlen + 2, CacheData[idx].dent, CacheData[idx].rent); return (CacheData[HitIdx = idx].dent); } /****************************************************************************/ /* */ char* GeoLocateData (int extend) { static char buf [128+32]; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GeoResponseData()\n"); cptr = GeoLocateResponse (NULL, NULL, NULL); if (!cptr) return ("\0bugcheck"); 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++ = ' '; } while (*cptr && *cptr != GEOSEP && sptr < zptr) *sptr++ = *cptr++; if (*cptr) cptr++; /* city */ if (*cptr != GEOSEP) { *sptr++ = ','; *sptr++ = ' '; } while (*cptr && *cptr != GEOSEP && sptr < zptr) *sptr++ = *cptr++; if (extend) { if (*cptr) cptr++; /* latitude */ if (*cptr != GEOSEP) { *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]", strerror(errno)); else sprintf (buf, "[%s]", gai_strerror(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]", strerror(errno)); else sprintf (buf, "[%s]", gai_strerror(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]", strerror(errno)); else sprintf (buf, "[%s]", gai_strerror(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]", strerror(errno)); return (buf); } /* free the addrinfo */ freeaddrinfo(aiptr); } return ("[bugcheck]"); } /*****************************************************************************/ /* By default UTF-8 is (partially) munged into 8 bit ASCII. If this is not required call this GeoLocateUtf8(1) before processing. */ int GeoLocateUtf8 (int reset) { static int utf8 = 1; if (!reset) return (utf8); return (utf8 = 0); } /*****************************************************************************/ /* 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); } } #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 */ /*****************************************************************************/