cosmopolitan/tool/net/lmaxmind.c
Justine Tunney 10fd8bdb70 Unbloat the build
This change resurrects ae5d06dc53
2022-08-11 00:15:29 -07:00

302 lines
9.5 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2021 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/mem/mem.h"
#include "libc/x/x.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lua.h"
#include "third_party/lua/luaconf.h"
#include "third_party/maxmind/maxminddb.h"
struct MaxmindDb {
int refs;
MMDB_s mmdb;
};
struct MaxmindResult {
uint32_t ip;
struct MaxmindDb *db;
MMDB_lookup_result_s mmlr;
};
static const char *GetMmdbError(int err) {
switch (err) {
case MMDB_FILE_OPEN_ERROR:
return "FILE_OPEN_ERROR";
case MMDB_CORRUPT_SEARCH_TREE_ERROR:
return "CORRUPT_SEARCH_TREE_ERROR";
case MMDB_INVALID_METADATA_ERROR:
return "INVALID_METADATA_ERROR";
case MMDB_IO_ERROR:
return "IO_ERROR";
case MMDB_OUT_OF_MEMORY_ERROR:
return "OUT_OF_MEMORY_ERROR";
case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR:
return "UNKNOWN_DATABASE_FORMAT_ERROR";
case MMDB_INVALID_DATA_ERROR:
return "INVALID_DATA_ERROR";
case MMDB_INVALID_LOOKUP_PATH_ERROR:
return "INVALID_LOOKUP_PATH_ERROR";
case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR:
return "LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR";
case MMDB_INVALID_NODE_NUMBER_ERROR:
return "INVALID_NODE_NUMBER_ERROR";
case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR:
return "IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR";
default:
return "UNKNOWN";
}
};
static int LuaMaxmindOpen(lua_State *L) {
int err;
const char *p;
struct MaxmindDb **udb, *db;
p = luaL_checklstring(L, 1, 0);
db = xmalloc(sizeof(struct MaxmindDb));
if ((err = MMDB_open(p, 0, &db->mmdb)) != MMDB_SUCCESS) {
free(db);
luaL_error(L, "MMDB_open(%s) → MMDB_%s", p, GetMmdbError(err));
unreachable;
}
db->refs = 1;
udb = lua_newuserdatauv(L, sizeof(db), 1);
luaL_setmetatable(L, "MaxmindDb*");
*udb = db;
return 1;
}
static wontreturn void LuaThrowMaxmindIpError(lua_State *L,
const char *function_name,
uint32_t ip, int err) {
luaL_error(L, "%s(%d.%d.%d.%d) → MMDB_%s", function_name,
(ip & 0xff000000) >> 030, (ip & 0x00ff0000) >> 020,
(ip & 0x0000ff00) >> 010, (ip & 0x000000ff) >> 000,
GetMmdbError(err));
unreachable;
}
static int LuaMaxmindDbLookup(lua_State *L) {
int err;
lua_Integer ip;
struct MaxmindDb **udb, *db;
struct MaxmindResult **ur, *r;
udb = luaL_checkudata(L, 1, "MaxmindDb*");
ip = luaL_checkinteger(L, 2);
if (ip < 0 || ip > 0xffffffff) {
lua_pushnil(L);
return 1;
}
db = *udb;
r = xmalloc(sizeof(struct MaxmindResult));
r->mmlr = MMDB_lookup(&db->mmdb, ip, &err);
if (err) {
free(r);
LuaThrowMaxmindIpError(L, "MMDB_lookup", ip, err);
}
if (!r->mmlr.found_entry) {
free(r);
lua_pushnil(L);
return 1;
}
r->ip = ip;
r->db = db;
r->db->refs++;
ur = lua_newuserdatauv(L, sizeof(r), 1);
luaL_setmetatable(L, "MaxmindResult*");
*ur = r;
return 1;
}
static int LuaMaxmindResultNetmask(lua_State *L) {
struct MaxmindResult **ur;
ur = luaL_checkudata(L, 1, "MaxmindResult*");
lua_pushinteger(L, (*ur)->mmlr.netmask - (128 - 32));
return 1;
}
static MMDB_entry_data_list_s *LuaMaxmindDump(lua_State *L,
MMDB_entry_data_list_s *dl) {
size_t i, n;
char ibuf[64];
switch (dl->entry_data.type) {
case MMDB_DATA_TYPE_UTF8_STRING:
lua_pushlstring(L, dl->entry_data.utf8_string, dl->entry_data.data_size);
return dl->next;
case MMDB_DATA_TYPE_BYTES:
lua_pushlstring(L, (void *)dl->entry_data.bytes,
dl->entry_data.data_size);
return dl->next;
case MMDB_DATA_TYPE_INT32:
lua_pushinteger(L, dl->entry_data.int32);
return dl->next;
case MMDB_DATA_TYPE_UINT16:
lua_pushinteger(L, dl->entry_data.uint16);
return dl->next;
case MMDB_DATA_TYPE_UINT32:
lua_pushinteger(L, dl->entry_data.uint32);
return dl->next;
case MMDB_DATA_TYPE_BOOLEAN:
lua_pushboolean(L, dl->entry_data.boolean);
return dl->next;
case MMDB_DATA_TYPE_UINT64:
lua_pushinteger(L, dl->entry_data.uint64);
return dl->next;
case MMDB_DATA_TYPE_UINT128:
sprintf(ibuf, "%#jjx", dl->entry_data.uint128);
lua_pushstring(L, ibuf);
return dl->next;
case MMDB_DATA_TYPE_DOUBLE:
lua_pushnumber(L, dl->entry_data.double_value);
return dl->next;
case MMDB_DATA_TYPE_FLOAT:
lua_pushnumber(L, dl->entry_data.float_value);
return dl->next;
case MMDB_DATA_TYPE_ARRAY:
lua_newtable(L);
n = dl->entry_data.data_size;
for (dl = dl->next, i = 0; dl && i < n; ++i) {
dl = LuaMaxmindDump(L, dl);
lua_seti(L, -2, i + 1);
}
return dl;
case MMDB_DATA_TYPE_MAP:
lua_newtable(L);
n = dl->entry_data.data_size;
for (dl = dl->next; dl && n; n--) {
dl = LuaMaxmindDump(L, dl);
dl = LuaMaxmindDump(L, dl);
lua_settable(L, -3);
}
return dl;
default:
lua_pushnil(L);
return dl->next;
}
}
static int LuaMaxmindResultGet(lua_State *L) {
int i, n, err;
const char **path;
MMDB_entry_s entry, *ep;
MMDB_entry_data_s edata;
struct MaxmindResult **ur;
MMDB_entry_data_list_s *dl;
n = lua_gettop(L) - 1;
ur = luaL_checkudata(L, 1, "MaxmindResult*");
if (n <= 0) {
ep = &(*ur)->mmlr.entry;
} else {
path = xcalloc(n + 1, sizeof(const char *));
for (i = 0; i < n; ++i) path[i] = lua_tostring(L, 2 + i);
err = MMDB_aget_value(&(*ur)->mmlr.entry, &edata, path);
free(path);
if (err) LuaThrowMaxmindIpError(L, "getpath", (*ur)->ip, err);
if (!edata.offset) {
lua_pushnil(L);
return 1;
}
entry.mmdb = (*ur)->mmlr.entry.mmdb;
entry.offset = edata.offset;
ep = &entry;
}
err = MMDB_get_entry_data_list(ep, &dl);
if (err) LuaThrowMaxmindIpError(L, "getlist", (*ur)->ip, err);
LuaMaxmindDump(L, dl);
MMDB_free_entry_data_list(dl);
return 1;
}
static void FreeMaxmindDb(struct MaxmindDb *db) {
if (!--db->refs) {
MMDB_close(&db->mmdb);
free(db);
}
}
static int LuaMaxmindDbGc(lua_State *L) {
struct MaxmindDb **udb;
udb = luaL_checkudata(L, 1, "MaxmindDb*");
if (*udb) {
FreeMaxmindDb(*udb);
*udb = 0;
}
return 0;
}
static int LuaMaxmindResultGc(lua_State *L) {
struct MaxmindResult **ur;
ur = luaL_checkudata(L, 1, "MaxmindResult*");
if (*ur) {
FreeMaxmindDb((*ur)->db);
free(*ur);
*ur = 0;
}
return 0;
}
static const luaL_Reg kLuaMaxmind[] = {
{"open", LuaMaxmindOpen}, //
{0}, //
};
static const luaL_Reg kLuaMaxmindDbMeth[] = {
{"lookup", LuaMaxmindDbLookup}, //
{0}, //
};
static const luaL_Reg kLuaMaxmindDbMeta[] = {
{"__gc", LuaMaxmindDbGc}, //
{0}, //
};
static const luaL_Reg kLuaMaxmindResultMeth[] = {
{"get", LuaMaxmindResultGet}, //
{"netmask", LuaMaxmindResultNetmask}, //
{0}, //
};
static const luaL_Reg kLuaMaxmindResultMeta[] = {
{"__gc", LuaMaxmindResultGc}, //
{0}, //
};
static void LuaMaxmindDb(lua_State *L) {
luaL_newmetatable(L, "MaxmindDb*");
luaL_setfuncs(L, kLuaMaxmindDbMeta, 0);
luaL_newlibtable(L, kLuaMaxmindDbMeth);
luaL_setfuncs(L, kLuaMaxmindDbMeth, 0);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
}
static void LuaMaxmindResult(lua_State *L) {
luaL_newmetatable(L, "MaxmindResult*");
luaL_setfuncs(L, kLuaMaxmindResultMeta, 0);
luaL_newlibtable(L, kLuaMaxmindResultMeth);
luaL_setfuncs(L, kLuaMaxmindResultMeth, 0);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
}
int LuaMaxmind(lua_State *L) {
luaL_newlib(L, kLuaMaxmind);
LuaMaxmindResult(L);
LuaMaxmindDb(L);
return 1;
}