mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
302 lines
9.5 KiB
C
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/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, "%#jx", 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;
|
||
|
}
|