#!/usr/bin/env bx
use b_plain
# FIXME: doesn't encode entities in dir listings, redirect message
cstr version = "0"
cstr listen_addr = "0.0.0.0"
int listen_port = 80
cstr listen_addr_stunnel = "127.0.0.1"
int listen_port_stunnel = 81
int listen_port_ssl = 443 # 8
cstr www_root = "/www"
cstr default_user = "www-data"
cstr default_host = "default"
cstr server_admin = "sam@nipl.net"
#cstr avfs_root = "/avfs"
cstr avfs_root = NULL
def avfs_char '^'
def avfs_char_cstr "^"
boolean allow_bots_avfs = 0
boolean allow_bots_query = 0
boolean allow_bad_bots = 0
boolean allow_bad_bots_robots = 1
# TODO allow_bots_binary ?
boolean allow_find = 0
boolean show_exe = 0
boolean show_exe = 0
boolean cgibin_exec = 1
boolean cgibin_secret = 1
boolean link_to_homepage = 0
cstr index_files[] = { "index.html", "index.cgi", "index.php", NULL }
Def text_types_list "b", "bb", "c", "h", "cc", "cpp", "pl", "pm", "py", "rb", "java", "sh", "diff", "patch", "lyx", "tex", "tsv", "csv"
Def bin_types_list "gz", "pnd"
Def zip_types_list "zip", "tar", "tgz", "tar.gz", "tbz2", "tar.bz2", "tar.Z", "rar", "ar", "deb", "jar", "7z", "rpm", "lha", "zoo", "arj"
Def z_types_list "gz", "bz2", "Z"
Def bots "google", "slurp", "check_http", "bot", "hosttracker", "yandex", "spider", "presto", "scanner", "phpcrawl", "bot", "lwp"
Def bad_bots "ahrefsbot", "ezooms", "dotbot", "mj12bot", "007ac9", "wotbox", "smtbot", "() {", "(){"
# use: stunnel -d 443 -r 81 -f
# or see etc/ for stunnel4 + sslh config
# use: avfsd -o allow_other /avfs
# note - to be useful with web, avfs was recompiled to use '!' not '#'
int n_servers = 1
#int n_servers = 2
int server_i
def verbose void
#def verbose warn
def wlog sf
hashtable *users
User *defu
hashtable _cache, *cache = &_cache
int cache_n_buckets = 1009
off_t cache_max_entry_size = 0 # 4096
struct cache_item
time_t mtime
buffer body
Main()
# memlog_file()
uid_t uid = geteuid()
if uid != 0
error("%s must be run as root", program)
# Nice(-20)
Clearenv()
Setenv("PATH", "/usr/local/bin:/usr/bin:/bin")
Setenv("SERVER_SOFTWARE", Format("%s/0", program))
Setenv("GATEWAY_INTERFACE", "CGI/1.1")
Setenv("SERVER_ADMIN", server_admin)
if avfs_root && !is_dir(avfs_root)
avfs_root = NULL
Setlinebuf(stdout)
sched_busy = 16
block_size = 16*1024
# block_size = 1024
max_line_length = 2048
# listen_backlog = 1
# Sigdfl_all()
ignore_pipe()
each(i, SIGINT, SIGQUIT, SIGTERM, SIGHUP)
sigact(i, term_handler)
sigact(SIGUSR1, usr1_handler)
date_rfc1123_init()
text_mimetypes()
bin_mimetypes()
load_mimetypes()
users = load_users()
defu = Get(users, default_user)
init(cache, hashtable, cstr_hash, cstr_eq, cache_n_buckets)
new(l, listener_tcp, listen_addr, listen_port)
new(w, httpd_launcher)
sh(sock_p, l, w)
new(l_https, listener_tcp, listen_addr_stunnel, listen_port_stunnel)
new(w_https, httpd_launcher)
sh(sock_p, l_https, w_https)
Setuser(defu)
for server_i=0; server_i < n_servers-1 ; ++server_i
if Fork() == 0
break
sched_init()
start(l) ; start(w)
start(l_https) ; start(w_https)
run()
Setuser_root()
term_handler(int sig)
kill(0, sig)
exit(0)
usr1_handler(int sig)
use(sig)
sched_exit()
proc httpd_launcher()
port sock_p in
state sock_p sk
repeat
rd(in, sk)
New(s, httpd, sk)
start(s)
proc httpd(sock *sk)
port buffer in
port buffer out
port buffer fin
port buffer fout
state int fd = sk->fd
state reader r
state writer w
state reader fr
state writer fw
state cat c1
state sockaddr_in sockname
state socklen_t namelen = sizeof(sockaddr_in)
state cstr scheme = "http"
state in_addr server_addr
state cstr server_addr_str
state int server_port
state in_addr remote_addr
state cstr remote_addr_str
state int remote_port
verbose("server %d accepted new connection", server_i)
# Rsleep(0.001) # FIXME shouldn't need to do this to get connections evenly distributed between the processes!
if getsockname(fd, &sockname, &namelen) == 0
if sockname.sin_family == AF_INET
server_addr = sockname.sin_addr
server_addr_str = Strdup(inet_ntoa(server_addr))
int port = ntohs(sockname.sin_port)
if listen_addr_stunnel && port == listen_port_stunnel
scheme = "https"
server_port = listen_port_ssl
else
server_port = port
remote_addr = sock_in_addr(sk)
remote_addr_str = Strdup(inet_ntoa(remote_addr))
remote_port = sock_in_port(sk)
nodelay(fd)
init(&r, reader, fd)
init(&w, writer, fd)
sh(buffer, &r, This)
sh(buffer, This, &w)
init(&in, buffer, block_size)
init(&out, buffer, block_size)
start(&r) ; start(&w)
req state cstr s
state char *c
state size_t l
state cstr method_str
state http__method method
state cstr url = NULL
state cstr proto = NULL
state cstr host = NULL
state cstr path = NULL
state cstr root = NULL
int root_len = 0
state cstr fullpath = NULL
state cstr query = NULL
state cstr user = default_user
state cstr pass = NULL
state off_t reqlen = -1
state int file_fd = -1
state User *u
state int code
state cstr msg
state cstr location = NULL
state int private = 0
state int expire_already = 0
state cstr user_agent = NULL
state cstr referer = NULL
state boolean keep_alive = 1
state boolean statable = 0
decl(st, Stats)
state boolean http1_1 = 1
state cstr body = NULL
state time_t mtime = -1
state cstr mtype = "text/plain"
state int status
state boolean range_req = 0
state long long int byte0 = 0
state long long int byte1
state boolean content_range_req = 0
state long long int cr_byte0 = 0
state long long int cr_byte1
state off_t fullsize
state off_t size
state off_t offset
state ssize_t count
state int line_start = 0
state num response_time
state pid_t child
Setuser_default() # TODO make this part of process lib
# exec_sshd()
# stop()
new(headers, vec, key_value, 32)
repeat
breadln(in, 0, c)
if !buflen(&in)
quit
s = buf0(&in)
l = c - s + 1
cstr_dos_to_unix(s)
if *s
break
buffer_shift(&in, l)
sched_set_time()
cstr *words = split(s, ' ')
if arylen(words) != 3
buffer_shift(&in, l)
Free(words)
bad
method_str = Strdup(words[0])
method = http_which_method(method_str)
url = Strdup(words[1]) ; proto = Strdup(words[2])
verbose("%s %s %s", method_str, url, scheme)
Free(words)
line_start = l
# check proto
if !strncmp(proto, "HTTP/", 5)
if !strcmp(proto+5, "1.0") || proto[5] == '0'
http1_1 = 0
keep_alive = 0
# read_headers
repeat
breadln(in, line_start, c)
if buflen(&in) <= line_start
quit
s = b(&in, line_start)
l = c - s + 1
cstr_dos_to_unix(s)
line_start += l
if !*s
break
verbose(s)
cstr *words = split(s, ':', 2)
if arylen(words) != 2
Free(words)
bad
cstr key = words[0], val = words[1]
Free(words)
while isspace(val[0])
++val
for char *i=key; *i; ++i
if tween(*i, 'A', 'Z')
*i += 'a' - 'A'
key_value *kv = vec_push(headers)
kv->k = Strdup(key) ; kv->v = Strdup(val)
if !strcmp(key, "host")
host = Strdup(val)
eif !strcmp(key, "authorization")
if !strncasecmp(val, "Basic ", 6)
decl(base64, buffer)
buffer_from_cstr(base64, val+6)
new(o, buffer, 32)
base64_decode_buffers(base64, o)
user = buffer_to_cstr(o)
cstr *user_pass = split(user, ':', 2)
if arylen(user_pass) != 2
Free(user_pass)
bad
pass = user_pass[1]
Free(user_pass)
eif !strcmp(key, "user-agent")
user_agent = Strdup(val)
eif !strcmp(key, "referer")
referer = Strdup(val)
eif !strcmp(key, "content-length")
reqlen = atoll(val)
eif !strcmp(key, "connection")
if !strcasecmp(val, "close")
keep_alive = 0
eif !strcasecmp(val, "Keep-Alive")
keep_alive = 1
eif !strcmp(key, "range")
int n = sscanf(val, "bytes=%lld-%lld", &byte0, &byte1) # FIXME safe?
if n
range_req = 1
if n == 1
byte1 = -1
eif !strcmp(key, "content-range")
int n = sscanf(val, "bytes=%lld-%lld", &cr_byte0, &cr_byte1) # FIXME safe?
if n
content_range_req = 1
if n == 1
cr_byte1 = -1
if host == NULL
host = get_host_from_url(url)
if host == NULL
host = Strdup(default_host)
path = get_path_from_url(url)
root = path_cat(www_root, host)
delimit(root, ':')
root_len = strlen(root)
# redirect to real vhost
Stats str
if !lstat(root, &str)
if S_ISLNK(str.st_mode)
cstr realhost = readlinks(Strdup(root), OE_CONT)
cstr free_me = realhost
size_t www_root_len = strlen(www_root)
if cstr_begins_with(realhost, www_root) && realhost[www_root_len] == '/'
realhost += www_root_len + 1
if !strchr(realhost, '/')
location = Format("%s://%s%s", scheme, base_name(realhost), path)
Free(free_me)
loc
Free(free_me)
else
notf
fullpath = path_cat(root, *path == '/' ? path+1 : path)
query = strchr(fullpath, '?')
if query
*query++ = '\0'
cstr q2 = strchr(path, '?')
if q2
*q2 = '\0'
query = Strdup(query)
if !allow_bots_query && is_bot(user_agent)
forbid
url_decode(path)
url_decode(fullpath)
fullpath = path_tidy(fullpath)
unless(cstr_begins_with(fullpath, root) && fullpath[root_len] == '/')
bad
for char *i=fullpath; *i; ++i
if *i == '/' && i[1] == '.'
forbid
if !allow_bad_bots && is_bad_bot(user_agent) && !(allow_bad_bots_robots && cstr_eq(path, "/robots.txt"))
forbid
# redirect /index.html -> / so each file has a single url
if cstr_ends_with(url, "/index.html")
location = Strdup(url)
strrchr(location, '/')[1] = '\0'
loc
# set user and respond
u = get(users, user)
if user != default_user && !(u && pass && auth(u, pass))
reqauth
if pass
bzero(pass, strlen(pass))
if user != default_user
Setuser_via_root(u)
boolean checked_redirect = 0
# avfs virtual filesystem
char real_path[PATH_MAX]
if avfs_root
char *p = strchr(fullpath, avfs_char)
if p && !exists(fullpath)
if !allow_bots_avfs
if is_bot(user_agent)
forbid
char *subpath = Strdup(p)
*p = '\0'
if !realpath(fullpath, real_path)
cstr target = readlink(fullpath) # FIXME improve this & merge with below
if target && (strstr(target, "://") || cstr_begins_with(target, "mailto:"))
# warn("realpath failed - trying %s", target)
location = Format("%s%s%s%s", target, subpath, query?"?":"", query?query:"")
Free(target) ; Free(subpath)
loc
Free(subpath)
accerr
if cstr_ends_with(fullpath, "/") && strcmp(real_path, "/")
int rplen = strlen(real_path)
real_path[rplen] = '/' ; real_path[rplen+1] = '\0'
# redirect to real path if necessary+possible TODO improve
if !strstr(real_path, "/.") && strcmp(fullpath, real_path) && cstr_begins_with(real_path, root) && real_path[root_len] == '/'
location = Format("%s://%s%s%s%s%s", scheme, host, real_path+root_len, subpath, query?"?":"", query?query:"")
Free(subpath)
loc
checked_redirect = 1
boolean can_read = test_read(real_path)
# warn("test_read %s -> %d", real_path, can_read)
Free(fullpath)
fullpath = Format("%s%s%s", avfs_root, real_path, subpath)
# warn("changed fullpath to %s for avfs", fullpath)
if !can_read
# avfs bug means if user can't read the
# archive, might still be allowed access based
# on the archive's perms. Don't allow that!
Free(subpath)
accerr
Free(subpath)
if !checked_redirect
if !realpath(fullpath, real_path)
cstr target = readlink(fullpath)
if target && (strstr(target, "://") || cstr_begins_with(target, "mailto:"))
# warn("realpath failed - trying %s", target)
location = Format("%s%s%s", target, query?"?":"", query?query:"")
Free(target)
loc
accerr
if cstr_ends_with(fullpath, "/") && strcmp(real_path, "/")
int rplen = strlen(real_path)
real_path[rplen] = '/' ; real_path[rplen+1] = '\0'
# warn("checking real_path %s for fullpath %s root %s root_len %d", real_path, fullpath, root, root_len)
# redirect to real path if necessary+possible TODO improve
if !strstr(real_path, "/.") && strcmp(fullpath, real_path) && cstr_begins_with(real_path, root) && real_path[root_len] == '/'
location = Format("%s://%s%s%s%s", scheme, host, real_path+root_len, query?"?":"", query?query:"")
loc
# index files
if fullpath[strlen(fullpath)-1] == '/'
cstr *index
for index = index_files; *index; ++index
cstr fullpath_index = cstr_cat(fullpath, *index)
statable = !stat(fullpath_index, st)
if statable && S_ISREG(st->st_mode)
Free(fullpath)
fullpath = fullpath_index
if method == HTTP_GET && !query && !strstr(*index, ".htm")
Free(query)
query = Strdup("")
else
Free(fullpath_index)
if !statable
statable = !stat(fullpath, st)
verbose(" %s", fullpath)
if statable && st->st_mode & S_IRUSR
mtime = st->st_mtime
fullsize = st->st_size
eif among(method, HTTP_GET, HTTP_HEAD, HTTP_DELETE)
if statable && !(st->st_mode & S_IRUSR)
errno = EPERM
accerr
else
fullsize = 0
if !(st->st_mode & S_IROTH)
private = 1
# if !user_agent || strcasestr(user_agent, "MSIE") || !strcasestr(user_agent, "Mozilla/5.0")
expire_already = 1
state cstr base = strrchr(fullpath, '/')
state cstr ext = strrchr(base, '.')
if ext
++ext
else
ext = ""
state boolean is_php = cstr_eq(ext, "php")
state boolean in_cgibin = cstr_begins_with(path, "/cgi-bin/") != NULL
if S_ISREG(st->st_mode) && (st->st_mode & S_IXUSR || is_php) && (method == HTTP_POST || (method == HTTP_GET && (query || (cgibin_exec && in_cgibin))))
get_cgi()
eif among(method, HTTP_PUT, HTTP_POST)
put_file()
eif method == HTTP_DELETE
delete_file()
eif !among(method, HTTP_GET, HTTP_HEAD) || reqlen > 0
bad
eif cgibin_secret && in_cgibin
forbid
eif S_ISREG(st->st_mode)
get_file()
eif allow_find && S_ISDIR(st->st_mode) && query
get_find()
eif S_ISDIR(st->st_mode)
get_dir()
else
notf
done Setuser_default()
Free(url) ; Free(proto) ; Free(host) ; Free(method_str)
Free(root) ; Free(fullpath) ; Free(body) ; Free(query)
if file_fd != -1
close(file_fd) ; file_fd = -1
key_value *i = vec0(headers)
key_value *e = vecend(headers)
for ; i!=e; ++i
Free(i->k) ; Free(i->v)
vec_free(headers)
if user != default_user
Free(user)
if keep_alive
buffer_shift(&in, line_start)
req
Free(server_addr_str)
Free(remote_addr_str)
shut
shut bclose(out)
quit
quit verbose("")
rm_fd(fd)
sock_free(sk)
Free(sk)
buffer_free(&in)
buffer_free(&out)
Free(This)
stop()
mesg body = Format("%s: %d %s\r\n", program, code, msg)
headers(code, msg, strlen(body))
bodymsg
bodymsg discard_req()
bcrlf(out)
bflush(out)
if method != HTTP_HEAD
bprint(out, body)
bflush(out)
if buflen(&out) # error
keep_alive = 0
done
ok code = 200 ; msg = "OK"
mesg
created code = 201 ; msg = "Created"
mesg
bad code = 400 ; msg = "Bad Request"
mesg
forbid code = 403 ; msg = "Forbidden"
mesg
notf code = 404 ; msg = "Not Found"
mesg
badrng code = 416 ; msg = "Requested Range Not Satisfiable"
mesg
srverr code = 500 ; msg = "Internal Server Error"
mesg
reqauth if !cstr_eq(scheme, "https")
https
body = Format("%s: 401 Unauthorized\r\n", program)
headers(401, "Unauthorized", strlen(body))
bsayf(out, "WWW-Authenticate: Basic realm=\"%s\"\r", host)
bodymsg
loc mtype = "text/html"
body = Format("%s: redirected to %s\r\n", program, location, location)
# headers(301, "Moved Permanently", strlen(body))
headers(302, "Found", strlen(body))
# headers(307, "Temporary Redirect", strlen(body))
bsayf(out, "Location: %s\r", location)
verbose(" %s", location)
Free(location)
bodymsg
https if listen_port_ssl == 443
location = Format("https://%s%s%s%s", host, path, query ? "?" : "", query ? query : "")
else
location = Format("https://%s:%d%s%s", host, listen_port_ssl, path, query ? "?" : "", query ? query : "")
loc
accerr which errno
EACCES reqauth
EPERM reqauth
ENOENT notf
ENOTDIR notf
ENAMETOOLONG bad
else srverr
def headers(code, msg, size)
Setuser_default()
# XXX this is BIG for a macro!
response_time = sched_get_time()
wlog("%lld\t%s\t%s\t%s\t%d %s\t%lld\t%s\t%s\t%s%s%s\t%s\t%s\t%s", (long long)response_time, host, user, remote_addr_str, code, msg, (long long)size, scheme, method_str, url, query?"?":"", query?query:"", referer?referer:"", location?location:"", user_agent?user_agent:"")
bsayf(out, "HTTP/1.1 %d %s\r", code, msg)
bsayf(out, "Date: %s\r", date_rfc1123((time_t)response_time))
if mtime != -1
bsayf(out, "Last-Modified: %s\r", date_rfc1123(mtime))
if range_req
bsayf(out, "Content-Range: %lld-%lld/%lld\r", (long long int)byte0, (long long int)byte1, (long long int)fullsize)
if size >= 0: # FIXME causes a compile warning
bsayf(out, "Content-Length: %lld\r", (long long int)size)
if code == 200 && expire_already
bsay(out, "Cache-Control: private, no-cache, no-store\r")
bsay(out, "Expires: 0\r")
if mtype && cstr_begins_with(mtype, "text/")
bsayf(out, "Content-Type: %s; charset=utf-8\r", mtype)
eif mtype
bsayf(out, "Content-Type: %s\r", mtype)
bsayf(out, "Server: %s\r", program)
if !http1_1
bsayf(out, "Connection: %s\r", keep_alive ? "Keep-Alive" : "close")
def get_file()
if *ext
cstr _mtype = mimetype(ext)
if _mtype
mtype = _mtype
state boolean in_cache = 0
state cache_item *citem = NULL
if method == HTTP_GET # not HTTP_HEAD
if fullsize < cache_max_entry_size && mtime < (time_t)sched_get_time() && user == default_user
key_value *kv = kv(cache, fullpath, NULL)
if kv->v == NULL
kv->k = strdup(fullpath)
citem = kv->v = Talloc(cache_item)
init(&citem->body, buffer, st->st_size)
else
# warn("cache hit")
citem = kv->v
if mtime == citem->mtime
in_cache = 1
if !in_cache
citem->mtime = mtime
buffer_set_size(&citem->body, fullsize)
buffer_squeeze(&citem->body)
file_fd = open(fullpath, O_RDONLY)
if file_fd == -1
# XXX FIXME this dance to avoid freeing clobbered pointers sucks,
# put it in a Del func or something?
void *k = kv->k, *v = kv->v
del(cache, fullpath)
Free(k) ; Free(v)
accerr
read(file_fd, buf0(&citem->body), fullsize)
# TODO don't ignore return value of read?
close(file_fd) ; file_fd = -1
# warn("loaded %lld bytes into cache", (long long int)fullsize)
in_cache = 1
else
file_fd = open(fullpath, O_RDONLY)
if file_fd == -1
accerr
cloexec(file_fd)
do_range_size()
if range_req
# if byte0
# Lseek(file_fd, byte0)
headers(206, "Partial Content", size)
else
headers(200, "OK", size)
bcrlf(out)
# bflush(out)
if method == HTTP_GET
if in_cache
struct iovec iov[2]
iov[0].iov_base = buf0(&out)
iov[0].iov_len = buflen(&out)
iov[1].iov_base = b(&citem->body, byte0)
iov[1].iov_len = size
nonblock(fd, 0)
Writev(fd, iov, 2)
nonblock(fd, 1)
bufclr(&out)
# bwrite_direct(out, b(&citem->body, byte0), b(&citem->body, byte0+size))
else
cork(fd)
bflush(out)
offset = byte0
while size
# size_t bytes = imin(size, sendfile_block_size)
count = sendfile(fd, file_fd, &offset, size)
if count == -1
if among(errno, ECONNRESET, EPIPE)
keep_alive = 0
break
eif errno != EAGAIN
swarning("sendfile %d %d %d %d", fd, file_fd, (int)offset, (int)size)
break
count = 0
# if count >= (ssize_t)block_size
# nodelay(fd)
if count < size
write(fd)
# else
# yield()
size -= count
cork(fd, 0)
# rm_fd(file_fd)
close(file_fd) ; file_fd = -1
else
bflush(out)
def dir_ends_slash_or_redirect()
if fullpath[strlen(fullpath)-1] != '/'
location = Format("%s/%s%s", url, query ? "?" : "", query ? query : "")
loc
def get_dir()
dir_ends_slash_or_redirect()
vec *v = ls(fullpath)
if !v
accerr
sort_vec(v, cstrp_cmp)
new(b1, buffer, 256)
new(b2, buffer, 256)
new(b3, buffer, 256)
# # main dir
# Sprintf(b1, "./ \n", path, path)
cstr *j = vec0(v)
cstr *e = vecend(v)
for ; j!=e; ++j
if (*j)[0] == '.'
continue
cstr entpath = path_cat(fullpath, *j)
int dir = 0
Stats ste
if !stat(entpath, &ste) && S_ISDIR(ste.st_mode)
dir = 1
if dir
Sprintf(b1, "%s", *j, *j)
if allow_find:
Sprintf(b1, "/", *j)
Sprintf(b1, " \n")
else
boolean exe = show_exe && ste.st_mode & S_IXUSR
Sprintf(b2, "%s%s%s%s \n", *j, *j, exe?"*":"")
if avfs_root && !strstr(*j, "/+")
int zip = is_zip_file(*j)
if zip
int find = allow_find && zip > 1
Sprintf(b3, "%s%s%s%s \n", *j, avfs_char, find?"/":"", *j, find?"/":"") # hm, ugly!
Free(entpath) ; Free(*j)
vec_free(v)
buffer_cat_cstr(b1, "\n
\n\n")
buffer_cat_cstr(b2, "\n
\n\n")
buffer_cat_cstr(b3, "\n
\n\n")
buffer_cat_range(b1, buffer_range(b2))
buffer_cat_range(b1, buffer_range(b3))
buffer_free(b2)
buffer_free(b3)
if link_to_homepage:
Sprintf(b1, "
%s
\n", scheme, host, host) # link to homepage
mtype = "text/html"
send_buffer(b1)
def send_buffer(b0)
fullsize = buflen(b0)
do_range_size()
if range_req
headers(206, "Partial Content", size)
else
headers(200, "OK", size)
bcrlf(out)
bflush(out)
if method == HTTP_GET # not HTTP_HEAD
bwrite(out, b(b0, byte0), b(b0, size))
bflush(out)
buffer_free(b0)
if buflen(&out) # error
keep_alive = 0
#def exec_sshd()
# pid_t child = Fork()
# if child == 0
# nonblock(fd, 0)
# Dup2(fd, STDIN_FILENO)
# Dup2(fd, STDOUT_FILENO)
## Dup2(fd, STDERR_FILENO)
#
# Sigdfl(SIGPIPE)
# exec__warn_fail = 0
# Execlp("/usr/sbin/sshd", "/usr/sbin/sshd", "-i", NULL)
# .
# waitchild(child, status)
def get_cgi() # FIXME!
srverr # XXX remove this!
state int cgi_sock[2]
Socketpair(cgi_sock) # we copy request content in for GET and POST
nonblock(cgi_sock[0])
nonblock(cgi_sock[1])
cloexec(cgi_sock[0])
cloexec(cgi_sock[1])
if add_fd(cgi_sock[0]) || add_fd(cgi_sock[1])
Close(cgi_sock[0]) ; Close(cgi_sock[1])
srverr
state ssize_t req_read = buflen(&in)-line_start
# warn("req_read: %d reqlen: %d", req_read, reqlen)
if reqlen >= 0 && reqlen < req_read
# warn("only part is of req")
req_read = reqlen
if user == default_user && getegid() != st->st_gid:
reqauth
child = Fork()
if child == 0
Setuidgid_via_root(u)
Setenv("USER", u->pw_name)
Setenv("LOGNAME", u->pw_name)
Setenv("HOME", u->pw_dir)
# cstr rb1 = b(&in, line_start)
# rb1[req_read] = '\0'
# Setenv("REQUEST_BODY_1", rb1) # FIXME XXX not compatible, doesn't cope with \0
if query
Setenv("QUERY_STRING", query)
set_cgi_vars()
nonblock(cgi_sock[1], 0)
Dup2(cgi_sock[1], STDIN_FILENO)
Dup2(cgi_sock[1], STDOUT_FILENO)
# Dup2(cgi_sock[1], STDERR_FILENO)
Sigdfl(SIGPIPE)
exec__warn_fail = 0
dirbase d_b = dirbasename(Strdup(fullpath))
if chdir(d_b.dir)
exit_exec_failed()
if is_php
Execlp("php", "php", fullpath, NULL)
Execl(fullpath, fullpath, NULL)
have_child(child)
Setuser_default()
Close(cgi_sock[1])
init(&fw, writer, cgi_sock[0])
sh(buffer, This, fout, &fw, in)
init(&fout, buffer, block_size)
start(&fw)
bwrite(fout, b(&in, line_start), b(&in, line_start+req_read))
bflush(fout)
line_start += req_read
init(&fr, reader, cgi_sock[0])
init(&c1, cat, reqlen)
# TODO a join or redirect function to simplify this plumbing stuff
buffer save_in = in
sh(buffer, &r, &c1) ; sh(buffer, &c1, &fw)
This->in->d = save_in
init(&c1.out->d, buffer, block_size)
sh(buffer, &fr, out, This, fin)
init(&fin, buffer, block_size)
start(&fr) ; start(&c1)
warn("about to read response from CGI script")
keep_alive = 0 # FIXME use chunked encoding and keep_alive
# TODO put this in its own proc?
state int cgi_response_started = 0
repeat
warn("looping reading response from CGI script")
bread(fin)
warn("1")
if !buflen(&fin)
break
warn(" len: %d", buflen(&fin))
buffer_dump(&fin)
if !cgi_response_started
cgi_response_started = 1
mtime = -1 ; mtype = NULL
headers(200, "OK", -1)
warn("2")
bwrite(out, buf0(&fin), bufend(&fin))
warn("3")
bflush(out)
warn("4")
bufclr(&fin)
if buflen(&out) # error
keep_alive = 0
break
warn("done reading response from CGI script")
buffer_free(&fin)
buffer_free(&fout)
buffer_free(&c1.out->d)
# FIXME this does not work well with keep-alive, because we don't count
# the content length first. FIXME I should use the "chunked" encoding
# if HTTP/1.1, eek.
.
warn("waiting for child to finish")
drop_child(child) # FIXME this is dodgy
waitchild(child, status)
# waitchild fails or blocks if child already exited :/
warn("child finished")
halt(&c1) ; halt(&fr) ; halt(&fw)
# await(&c1) ; await(&fr) ; await(&fw) # FIXME busy waits!
save_in = in
sh(buffer, &r, This)
in = save_in
if status_execfailed(status)
reqauth
else
# # This is a bit bogus, when can we use keep_alive for POST?
# # keep_alive can work if the whole request was read by tachyon,
# # if the CGI script gets to read on the socket, the next req
# # would get buffered and lost.
# # I would need a way for the script to pass any extra buffered
# # input back to tachyon! unread() would be nice...
#
# keep_alive = keep_alive && reqlen >= 0 && req_read >= reqlen
# warn("keep_alive: %d", keep_alive)
if status != 0
srverr # maybe too late!
def put_file()
int open_opt = O_WRONLY|O_CREAT|O_NONBLOCK
if content_range_req
put_do_range()
else
if method == HTTP_PUT
open_opt |= O_TRUNC
else # method == HTTP_POST
open_opt |= O_APPEND
file_fd = open(fullpath, open_opt, 0666)
Setuser_default()
if content_range_req
Lseek(file_fd, cr_byte0)
if file_fd == -1
accerr
if add_fd(file_fd)
Close(file_fd) ; file_fd = -1
srverr
cloexec(file_fd)
init(&fw, writer, file_fd)
sh(buffer, This, fout, &fw, in)
# init(&fout, buffer, block_size)
start(&fw)
let(out_tmp, out)
offset = 0
if buflen(&in) <= line_start
push(in)
if reqlen != 0
repeat
pull(in)
if buflen(&in) <= line_start
break
count = buflen(&in)
if reqlen > 0 && (off_t)(offset + count) > reqlen
count = reqlen - offset
offset += count
bwrite_direct(fout, b(&in, line_start), b(&in, line_start+count))
# bflush(fout)
if buflen(&fout) # error
break
buffer_set_size(&in, line_start)
if offset == reqlen
break
push(in)
# buffer_free(&fout) XXX isn't this needed?
out = out_tmp
if content_range_req && method == HTTP_PUT && cr_byte1 >= fullsize-1
Ftruncate(file_fd, cr_byte0 + offset)
if file_fd != -1
rm_fd(file_fd)
if close(file_fd)
swarning("close failed for file: %s", fullpath)
file_fd = -1
if reqlen == -1
keep_alive = 0
eif offset < reqlen
reqlen = 0
bad
reqlen = 0
if statable
ok
else
created
def delete_file()
int failed = remove(fullpath)
if failed
accerr
ok
def discard_req()
if reqlen > 0
bread(in, reqlen)
buffer_set_size(&in, line_start)
def do_range_size()
if range_req
fix_byte_range(byte0) ; fix_byte_range(byte1)
if byte1 >= fullsize
byte1 = fullsize - 1
size = byte1 - byte0 + 1
if byte1 <= byte0
badrng
else
size = fullsize
def put_do_range()
if range_req
fix_byte_range(cr_byte0) ; fix_byte_range(cr_byte1)
if cr_byte1 <= cr_byte0
bad
def fix_byte_range(b)
if b < 0
b += fullsize
if b < 0
b = 0
def Setuser_default()
# if user != default_user
if geteuid() != defu->pw_uid
Setuser_via_root(defu)
text_mimetypes()
# some mimetypes that I want the browser to display as text/plain
mimetypes_init()
sym_init()
cstr type = sym("text/plain")
each(e, (char *)text_types_list)
kv(mimetypes, sym(e), type)
bin_mimetypes()
# some mimetypes that I want the browser to display as application/octet-stream
mimetypes_init()
sym_init()
cstr type = sym("application/octet-stream")
each(e, (char *)bin_types_list)
kv(mimetypes, sym(e), type)
def set_cgi_vars()
char tmp[1024]
Setenv("SERVER_NAME", host)
Setenv("SERVER_PROTOCOL", proto)
Setenv("REQUEST_METHOD", method_str)
# if (path_info) {
# Setenv("PATH_INFO", "/foo/bar")
# Setenv("PATH_TRANSLATED", "/www/sam.nipl.net/foo/bar")
# }
Setenv("SCRIPT_NAME", path)
# Setenv("REMOTE_HOST", "...") # blank if not found, or omit it
Setenv("REMOTE_ADDR", remote_addr_str)
# TODO
# Setenv("AUTH_TYPE", "basic")
# Setenv("REMOTE_USER", "sam")
# Setenv("REMOTE_IDENT", "sam") # omit?
if (method == HTTP_POST) {
# TODO
# Setenv("CONTENT_TYPE", "form/url-encoded") # of form only if submitted
sprintf(tmp, "%lld", (long long int)reqlen)
Setenv("CONTENT_LENGTH", tmp)
}
# non-standard or CGI/1.1
snprintf(tmp, sizeof(tmp), "