added ``block return'' and ``strip'' options

This commit is contained in:
Omar Polo 2021-02-06 17:22:37 +00:00
parent daac4a9452
commit 6abda252e9
8 changed files with 275 additions and 5 deletions

View File

@ -1,3 +1,7 @@
2021-02-06 Omar Polo <op@omarpolo.com>
* parse.y (locopt): added ``block return'' and ``strip'' options
2021-02-05 Omar Polo <op@omarpolo.com>
* iri.c (parse_query): don't %-decode the query part. This affects the value of QUERY_STRING for CGI scripts too, since that must be %-encoded and we're currently shipping it decoded.

38
gmid.1
View File

@ -231,6 +231,44 @@ A
section may include most of the server configuration rules
except
.Ic cert , Ic key , Ic root , Ic location No and Ic cgi .
.It Ic block Op Ic return Ar code Op Ar meta
Send a reply and close the connection;
.Ar code
is 40
and
.Ar meta
is
.Dq temporary failure
by default.
If
.Ar code
is in the 3x range, then
.Ar meta
must be provided.
Inside
.Ar meta ,
the following special sequences are replaced:
.Bl -tag -compact
.It %%
is replaced with a single
.Sq % .
.It %p
is replaced with the request path.
.It %q
is replaced with the query string of the request.
.It %P
is replaced with the server port.
.It %N
is replaced with the server name.
.El
.It Ic strip Ar number
Strip
.Ar number
components from the beginning of the path.
It's only considered for the
.Ar meta
parameter in the scope of a matching
.Ic block return .
.El
.Sh CGI
When a request for an executable file matches the

1
gmid.c
View File

@ -439,6 +439,7 @@ free_config(void)
free((char*)l->lang);
free((char*)l->default_mime);
free((char*)l->index);
free((char*)l->block_fmt);
}
}
memset(hosts, 0, sizeof(hosts));

7
gmid.h
View File

@ -69,6 +69,9 @@ struct location {
const char *default_mime;
const char *index;
int auto_index; /* 0 auto, -1 off, 1 on */
int block_code;
const char *block_fmt;
int strip;
};
struct vhost {
@ -208,7 +211,7 @@ extern int yylineno;
extern int yyparse(void);
extern int yylex(void);
void yyerror(const char*);
void yyerror(const char*, ...);
int parse_portno(const char*);
void parse_conf(const char*);
@ -223,6 +226,8 @@ const char *vhost_lang(struct vhost*, const char*);
const char *vhost_default_mime(struct vhost*, const char*);
const char *vhost_index(struct vhost*, const char*);
int vhost_auto_index(struct vhost*, const char*);
int vhost_block_return(struct vhost*, const char*, int*, const char**);
int vhost_strip(struct vhost*, const char*);
void mark_nonblock(int);
void loop(struct tls*, int, int);

3
lex.l
View File

@ -69,6 +69,9 @@ cgi return TCGI;
lang return TLANG;
index return TINDEX;
auto return TAUTO;
strip return TSTRIP;
block return TBLOCK;
return return TRETURN;
[{}] return *yytext;

77
parse.y
View File

@ -18,6 +18,7 @@
*/
#include <err.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
@ -36,10 +37,13 @@ size_t iloc;
int goterror = 0;
const char *config_path;
void yyerror(const char*);
void yyerror(const char*, ...);
int parse_portno(const char*);
void parse_conf(const char*);
char *ensure_absolute_path(char*);
int check_block_code(int);
char *check_block_fmt(char*);
int check_strip_no(int);
%}
@ -54,6 +58,7 @@ char *ensure_absolute_path(char*);
%token TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE
%token TCHROOT TUSER TSERVER
%token TLOCATION TCERT TKEY TROOT TCGI TLANG TINDEX TAUTO
%token TSTRIP TBLOCK TRETURN
%token TERR
%token <str> TSTRING
@ -156,15 +161,42 @@ locopt : TDEFAULT TTYPE TSTRING {
loc->index = $2;
}
| TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; }
| TBLOCK TRETURN TNUM TSTRING {
if (loc->block_fmt != NULL)
yyerror("`block' rule specified more than once");
loc->block_fmt = check_block_fmt($4);
loc->block_code = check_block_code($3);
}
| TBLOCK TRETURN TNUM {
if (loc->block_fmt != NULL)
yyerror("`block' rule specified more than once");
loc->block_fmt = xstrdup("temporary failure");
loc->block_code = check_block_code($3);
if ($3 >= 30 && $3 < 40)
yyerror("missing `meta' for block return %d", $3);
}
| TBLOCK {
if (loc->block_fmt != NULL)
yyerror("`block' rule specified more than once");
loc->block_fmt = xstrdup("temporary failure");
loc->block_code = 40;
}
| TSTRIP TNUM { loc->strip = check_strip_no($2); }
;
%%
void
yyerror(const char *msg)
yyerror(const char *msg, ...)
{
va_list ap;
goterror = 1;
fprintf(stderr, "%s:%d: %s\n", config_path, yylineno, msg);
va_start(ap, msg);
fprintf(stderr, "%s:%d: ", config_path, yylineno);
vfprintf(stderr, msg, ap);
va_end(ap);
}
int
@ -204,3 +236,42 @@ ensure_absolute_path(char *path)
yyerror("not an absolute path");
return path;
}
int
check_block_code(int n)
{
if (n < 10 || n >= 70 || (n >= 20 && n <= 29))
yyerror("invalid block code %d", n);
return n;
}
char *
check_block_fmt(char *fmt)
{
char *s;
for (s = fmt; *s; ++s) {
if (*s != '%')
continue;
switch (*++s) {
case '%':
case 'p':
case 'q':
case 'P':
case 'N':
break;
default:
yyerror("invalid format specifier %%%c", *s);
}
}
return fmt;
}
int
check_strip_no(int n)
{
if (n <= 0)
yyerror("invalid strip number %d", n);
return n;
}

View File

@ -220,3 +220,41 @@ echo OK GET /dir/ with auto index on
check "should be running"
quit
# test block return and strip
config '' 'location "*" { block }'
checkconf
run
eq "$(head /)" "40 temporary failure" "Unexpected head for /"
eq "$(get /)" "" "Unexpected body for /"
echo OK GET / with block
eq "$(head /nonexists)" "40 temporary failure" "Unexpected head for /nonexists"
eq "$(get /nonexists)" "" "Unexpected body for /nonexists"
echo OK GET /nonexists with block
check "should be running"
quit
config '' '
location "/dir" {
strip 1
block return 40 "%% %p %q %P %N test"
}
location "*" {
strip 99
block return 40 "%% %p %q %P %N test"
}'
checkconf
run
eq "$(head /dir/foo.gmi)" "40 % /foo.gmi 10965 localhost test"
echo OK GET /dir/foo.gmi with strip and block
eq "$(head /bigfile)" "40 % 10965 localhost test"
echo OK GET /bigfile with strip and block
check "should be running"
quit

112
server.c
View File

@ -35,6 +35,7 @@ static void open_file(struct pollfd*, struct client*);
static void load_file(struct pollfd*, struct client*);
static void check_for_cgi(struct pollfd*, struct client*);
static void handle_handshake(struct pollfd*, struct client*);
static int apply_block_return(struct pollfd*, struct client*);
static void handle_open_conn(struct pollfd*, struct client*);
static void start_reply(struct pollfd*, struct client*, int, const char*);
static void handle_start_reply(struct pollfd*, struct client*);
@ -131,6 +132,47 @@ vhost_auto_index(struct vhost *v, const char *path)
return v->locations[0].auto_index == 1;
}
int
vhost_block_return(struct vhost *v, const char *path, int *code, const char **fmt)
{
struct location *loc;
if (v == NULL || path == NULL)
return 0;
for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
if (!fnmatch(loc->match, path, 0)) {
if (loc->block_code != 0) {
*code = loc->block_code;
*fmt = loc->block_fmt;
return 1;
}
}
}
*code = v->locations[0].block_code;
*fmt = v->locations[0].block_fmt;
return v->locations[0].block_code != 0;
}
int
vhost_strip(struct vhost *v, const char *path)
{
struct location *loc;
if (v == NULL || path == NULL)
return 0;
for (loc = &v->locations[1]; loc->match != NULL; ++loc) {
if (!fnmatch(loc->match, path, 0)) {
if (loc->strip != 0)
return loc->strip;
}
}
return v->locations[0].strip;
}
static int
check_path(struct client *c, const char *path, int *fd)
{
@ -333,6 +375,73 @@ err:
start_reply(fds, c, BAD_REQUEST, "Wrong/malformed host or missing SNI");
}
/* 1 if a matching `block return' (and apply it), 0 otherwise */
static int
apply_block_return(struct pollfd *fds, struct client *c)
{
char *t, *path, buf[32];
const char *fmt;
int strip, code;
size_t i;
if (!vhost_block_return(c->host, c->iri.path, &code, &fmt))
return 0;
strip = vhost_strip(c->host, c->iri.path);
path = c->iri.path;
while (strip > 0) {
if ((t = strchr(path, '/')) == NULL) {
path = strchr(path, '\0');
break;
}
path = t;
strip--;
}
memset(buf, 0, sizeof(buf));
for (i = 0; *fmt; ++fmt) {
if (i == sizeof(buf)-1 || *fmt == '%') {
strlcat(c->sbuf, buf, sizeof(c->sbuf));
memset(buf, 0, sizeof(buf));
i = 0;
}
if (*fmt != '%') {
buf[i++] = *fmt;
continue;
}
switch (*++fmt) {
case '%':
strlcat(c->sbuf, "%", sizeof(c->sbuf));
break;
case 'p':
strlcat(c->sbuf, path, sizeof(c->sbuf));
break;
case 'q':
strlcat(c->sbuf, c->iri.query, sizeof(c->sbuf));
break;
case 'P':
snprintf(buf, sizeof(buf), "%d", conf.port);
strlcat(c->sbuf, buf, sizeof(c->sbuf));
memset(buf, 0, sizeof(buf));
break;
case 'N':
strlcat(c->sbuf, c->domain, sizeof(c->sbuf));
break;
default:
fatal("%s: unknown fmt specifier %c",
__func__, *fmt);
}
}
if (i != 0)
strlcat(c->sbuf, buf, sizeof(c->sbuf));
start_reply(fds, c, code, c->sbuf);
return 1;
}
static void
handle_open_conn(struct pollfd *fds, struct client *c)
{
@ -372,7 +481,8 @@ handle_open_conn(struct pollfd *fds, struct client *c)
return;
}
open_file(fds, c);
if (!apply_block_return(fds, c))
open_file(fds, c);
}
static void