diff options
Diffstat (limited to 'src/detection/publicip/publicip.c')
| -rw-r--r-- | src/detection/publicip/publicip.c | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c new file mode 100644 index 0000000..abca57c --- /dev/null +++ b/src/detection/publicip/publicip.c @@ -0,0 +1,95 @@ +#include "publicip.h" +#include "common/networking.h" + +#define FF_UNINITIALIZED ((const char*) (uintptr_t) -1) +static FFNetworkingState states[2]; +static const char* statuses[2] = { FF_UNINITIALIZED, FF_UNINITIALIZED }; + +void ffPreparePublicIp(FFPublicIPOptions* options) { + FFNetworkingState* state = &states[options->ipv6]; + const char** status = &statuses[options->ipv6]; + if (*status != FF_UNINITIALIZED) { + fputs("Error: PublicIp module can only be used once due to internal limitations\n", stderr); + exit(1); + } + + state->timeout = options->timeout; + state->ipv6 = options->ipv6; + + if (options->url.length == 0) { + state->compression = true; + state->tfo = true; + *status = ffNetworkingSendHttpRequest(state, options->ipv6 ? "v6.ipinfo.io" : "ipinfo.io", "/json", NULL); + } else { + FF_STRBUF_AUTO_DESTROY host = ffStrbufCreateCopy(&options->url); + uint32_t hostStartIndex = ffStrbufFirstIndexS(&host, "://"); + if (hostStartIndex < host.length) { + if (hostStartIndex != 4 || !ffStrbufStartsWithIgnCaseS(&host, "http")) { + fputs("Error: only http: protocol is supported. Use `Command` module with `curl` if needed\n", stderr); + exit(1); + } + ffStrbufSubstrAfter(&host, hostStartIndex + (uint32_t) (strlen("://") - 1)); + } + uint32_t pathStartIndex = ffStrbufFirstIndexC(&host, '/'); + + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); + if (pathStartIndex != host.length) { + ffStrbufAppendNS(&path, pathStartIndex, host.chars + (host.length - pathStartIndex)); + host.length = pathStartIndex; + host.chars[pathStartIndex] = '\0'; + } + + *status = ffNetworkingSendHttpRequest(state, host.chars, path.length == 0 ? "/" : path.chars, NULL); + } +} + +static inline void wrapYyjsonFree(yyjson_doc** doc) { + assert(doc); + if (*doc) { + yyjson_doc_free(*doc); + } +} + +const char* ffDetectPublicIp(FFPublicIPOptions* options, FFPublicIpResult* result) { + FFNetworkingState* state = &states[options->ipv6]; + const char** status = &statuses[options->ipv6]; + if (*status == FF_UNINITIALIZED) { + ffPreparePublicIp(options); + } + + if (*status != NULL) { + return *status; + } + + FF_STRBUF_AUTO_DESTROY response = ffStrbufCreateA(4096); + const char* error = ffNetworkingRecvHttpResponse(state, &response); + + *state = (FFNetworkingState) {}; + *status = FF_UNINITIALIZED; + + if (error == NULL) { + ffStrbufSubstrAfterFirstS(&response, "\r\n\r\n"); + } else { + return error; + } + + if (response.length == 0) { + return "Empty server response received"; + } + + if (options->url.length == 0) { + yyjson_doc* FF_A_CLEANUP(wrapYyjsonFree) doc = yyjson_read_opts(response.chars, response.length, 0, NULL, NULL); + if (doc) { + yyjson_val* root = yyjson_doc_get_root(doc); + ffStrbufAppendJsonVal(&result->ip, yyjson_obj_get(root, "ip")); + ffStrbufDestroy(&result->location); + ffStrbufInitF(&result->location, "%s, %s", yyjson_get_str(yyjson_obj_get(root, "city")), yyjson_get_str(yyjson_obj_get(root, "country"))); + return NULL; + } + } + + ffStrbufDestroy(&result->ip); + ffStrbufInitMove(&result->ip, &response); + ffStrbufTrimRightSpace(&result->ip); + return NULL; +} |