Add finger demo to redbean and fix regression

This change fixes a regression in unix.connect() caused by the recent
addition of UNIX domain sockets. The BSD finger command has been added
to third_party for fun and profit. A new demo has been added to redbean
showing how a protocol as simple as finger can be implemented.
This commit is contained in:
Justine Tunney 2022-06-23 03:39:44 -07:00
parent 2415afab0e
commit 17cbe73411
34 changed files with 2454 additions and 29 deletions

View file

@ -0,0 +1,70 @@
-- UNIX Finger Example
local function WriteForm(host, user)
Write([[<!doctype html>
<title>redbean unix finger demo</title>
<style>
body { padding: 1em; }
h1 a { color: inherit; text-decoration: none; }
h1 img { border: none; vertical-align: middle; }
input { margin: .5em; padding: .25em; }
pre { margin-left: 2em; }
p { word-break: break-word; max-width: 650px; }
dt { font-weight: bold; }
dd { margin-top: 1em; margin-bottom: 1em; }
.hdr { text-indent: -1em; padding-left: 1em; }
</style>
<h1>
<a href="/"><img src="/redbean.png"></a>
<a href="unix-finger.lua">redbean unix finger demo</a>
</h1>
<p>
Your redbean is able to function as an finger client. Lua server
pages can use the <code>unix</code> module to implement protocols
that your redbean wasn't originally intended to support. All it
takes is few lines of code!
</p>
<form action="unix-finger.lua" method="post">
<input type="text" id="host" name="host" size="40"
value="%s" placeholder="host" autofocus>
<label for="host">host</label>
<br>
<input type="text" id="user" name="user" size="40"
value="%s" placeholder="user">
<label for="user">user</label>
<br>
<input type="submit" value="finger">
</form>
]] % {EscapeHtml(host), EscapeHtml(user)})
end
local function main()
if IsPublicIp(GetClientAddr()) then
ServeError(403)
elseif GetMethod() == 'GET' or GetMethod() == 'HEAD' then
WriteForm('graph.no', 'new_york')
elseif GetMethod() == 'POST' then
ip = assert(ResolveIp(GetParam('host')))
fd = assert(unix.socket())
assert(unix.connect(fd, ip, 79))
assert(unix.write(fd, GetParam('user') .. '\r\n'))
response = ''
while true do
data = assert(unix.read(fd))
if data == '' then
break
end
response = response .. data
end
assert(unix.close(fd))
WriteForm(GetParam('host'), GetParam('user'))
Write('<pre>\r\n')
Write(EscapeHtml(VisualizeControlCodes(response)))
Write('</pre>\r\n')
else
ServeError(405)
SetHeader('Allow', 'GET, HEAD, POST')
end
end
main()

View file

@ -1449,6 +1449,28 @@ FUNCTIONS
value will be the `"0b"`-prefixed binary str. The result is
currently modulo 2^64. Negative numbers are converted to unsigned.
ResolveIp(hostname:str)
├─→ ip:uint32
└─→ nil, error:str
Gets IP address associated with hostname.
This function first checks if hostname is already an IP address, in
which case it returns the result of `ParseIp`. Otherwise, it checks
HOSTS.TXT on the local system and returns the first IPv4 address
associated with hostname. If no such entry is found, a DNS lookup is
performed using the system configured (e.g. /etc/resolv.conf) DNS
resolution service. If the service returns multiple IN A records
then only the first one is reutrned.
The returned address is word-encoded in host endian order. For
example, 1.2.3.4 is encoded as 0x01020304. The `FormatIp` function
may be used to turn this value back into a string.
If no IP address could be found, then nil is returned alongside a
string of unspecified format describing the error. Calls to this
function may be wrapped in assert() if an exception is desired.
────────────────────────────────────────────────────────────────────────────────

View file

@ -21,6 +21,7 @@
#include "libc/bits/popcnt.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/rusage.h"
#include "libc/dns/dns.h"
#include "libc/fmt/itoa.h"
#include "libc/fmt/leb128.h"
#include "libc/intrin/kprintf.h"
@ -41,7 +42,9 @@
#include "libc/runtime/sysconf.h"
#include "libc/sock/sock.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/consts/sock.h"
#include "libc/time/time.h"
#include "libc/x/x.h"
#include "net/http/escape.h"
@ -379,6 +382,27 @@ int LuaSlurp(lua_State *L) {
}
}
int LuaResolveIp(lua_State *L) {
ssize_t rc;
int64_t ip;
const char *host;
struct addrinfo *ai = NULL;
struct addrinfo hint = {AI_NUMERICSERV, AF_INET, SOCK_STREAM, IPPROTO_TCP};
host = luaL_checkstring(L, 1);
if ((ip = ParseIp(host, -1)) != -1) {
lua_pushinteger(L, ntohl(ai->ai_addr4->sin_addr.s_addr));
return 1;
} else if ((rc = getaddrinfo(host, "0", &hint, &ai)) == EAI_SUCCESS) {
lua_pushinteger(L, ntohl(ai->ai_addr4->sin_addr.s_addr));
freeaddrinfo(ai);
return 1;
} else {
lua_pushnil(L);
lua_pushfstring(L, "%s: DNS lookup failed: EAI_%s", host, gai_strerror(rc));
return 2;
}
}
static int LuaCheckControlFlags(lua_State *L, int idx) {
int f = luaL_checkinteger(L, idx);
if (f & ~(kControlWs | kControlC0 | kControlC1)) {

View file

@ -70,6 +70,7 @@ int LuaRand64(lua_State *);
int LuaRdrand(lua_State *);
int LuaRdseed(lua_State *);
int LuaRdtsc(lua_State *);
int LuaResolveIp(lua_State *);
int LuaSetLogLevel(lua_State *);
int LuaSha1(lua_State *);
int LuaSha224(lua_State *);

View file

@ -173,6 +173,7 @@ o/$(MODE)/tool/net/demo/unix-subprocess.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-webserver.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-dir.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-info.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-finger.lua.zip.o \
o/$(MODE)/tool/net/demo/fetch.lua.zip.o \
o/$(MODE)/tool/net/demo/call-lua-module.lua.zip.o \
o/$(MODE)/tool/net/demo/store-asset.lua.zip.o \
@ -222,6 +223,7 @@ o/$(MODE)/tool/net/redbean-demo.com.dbg: \
o/$(MODE)/tool/net/demo/unix-webserver.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-dir.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-info.lua.zip.o \
o/$(MODE)/tool/net/demo/unix-finger.lua.zip.o \
o/$(MODE)/tool/net/demo/fetch.lua.zip.o \
o/$(MODE)/tool/net/demo/store-asset.lua.zip.o \
o/$(MODE)/tool/net/demo/call-lua-module.lua.zip.o \

View file

@ -5131,6 +5131,7 @@ static const luaL_Reg kLuaFuncs[] = {
{"Rdrand", LuaRdrand}, //
{"Rdseed", LuaRdseed}, //
{"Rdtsc", LuaRdtsc}, //
{"ResolveIp", LuaResolveIp}, //
{"Route", LuaRoute}, //
{"RouteHost", LuaRouteHost}, //
{"RoutePath", LuaRoutePath}, //