Add `gemini_envinit', add proxy support, simplfy header handling

Add `gemini_envinit' which initializes some parts of the structure
from the environment.
Add proxy support in the form of a `proxy' field in the structure
that can hold a host string.
Simplfy header handling in `gemini_read', removing the
`GEMINI_STRICT' flag.
Add the `-h' option to gemini-cat(1) to set the proxy host.
Remove the `-e' option from gemini-cat(1).
Add environment variable support to gemini-cat(1), including
`GEMINI_TOFU_WRITE', which will set the corresponding flag.
This commit is contained in:
styan 2020-06-20 04:56:34 +00:00
parent e0d7ff540e
commit 5dcddc7077
5 changed files with 238 additions and 132 deletions

View File

@ -1,4 +1,4 @@
.Dd Jun 10, 2020
.Dd Jun 20, 2020
.Dt GEMINI-CAT 1
.Os
.Sh NAME
@ -6,8 +6,9 @@
.Nd concatnate responses from a Gemini URLs
.Sh SYNOPSIS
.Nm
.Op Fl RSeisw
.Op Fl RSisw
.Op Fl c Ar cert-file
.Op Fl h Ar proxy-host
.Op Fl k Ar key-file
.Op Fl p Ar port
.Op Fl r Ar max-redirects
@ -26,8 +27,10 @@ Strip out non-space control-characters before writing, the default for
TTY outputs.
.It Fl c Ar cert-file
The certificate file to use for TLS.
.It Fl e
Enforce a strict interpretation of the Gemini specification.
.It Fl h Ar proxy-host
The host to connect through.
.Ar proxy-host
can also specify a port number.
.It Fl i
Interactive mode (allow prompts).
.It Fl k Ar key-file
@ -47,6 +50,30 @@ The file to read host certificate hashes from.
Write any new hashes to the
.Ar tofu-file .
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev GEMINI_KEYFILE
Sets the TLS key file before
.Fl k .
.It Ev GEMINI_CERTFILE
Sets the TLS certificate file before
.Fl c .
.It Ev GEMINI_TOFUFILE
Sets the TOFU tile before
.Fl t .
.It Ev GEMINI_PROXY
Sets the proxy host before
.Fl h .
.It Ev GEMINI_MAX_REDIRECTS
Sets the maximum number of redirects before
.Fl r .
.It Ev GEMINI_PORT
Sets the default port number before
.Fl p .
.It Ev GEMINI_TOFU_WRITE
If set to a non-empty string, it is equivalent to
.Fl w .
.El
.Sh EXIT STATUS
.Ex -std
.Sh EXAMPLES
@ -54,6 +81,15 @@ Fetch the
.Lk tilde.black
root index:
.Dl gemini-cat gemini://tilde.black/ | more
Fetch
.Lk tilde.black
from its onion address:
.Bd -literal -offset indent -compact
torsocks gemini-cat -h \\
black6kfjetfuzaeozz7fs53whh7xtd4e27telrf5fg5kgdt5ah5plad.onion \\
//tilde.black/
.Ed
.Sh SEE ALSO
.Xr tls_config_set_cert_file 3
.Xr libgeminiclient 3 ,
.Xr tls_config_set_cert_file 3 ,
.Xr tls_config_set_key_file 3

View File

@ -32,19 +32,23 @@ main(int argc, char *argv[])
char b[BUFFER_SIZE];
struct gemini gemini = GEMINI_INITIALIZER;
const char *query = NULL;
char *p;
ssize_t r;
size_t i;
int prompt = 0;
int rawout;
int c;
gemini_envinit(&gemini);
if ((p = getenv("GEMINI_TOFU_WRITE")) != NULL && *p != '\0')
gemini.flags |= GEMINI_TOFU_WRITE;
rawout = !isatty(STDOUT_FILENO);
#ifdef __OpenBSD__
if (pledge("stdio cpath rpath wpath flock inet dns tty", NULL)
!= 0)
err(EX_NOPERM, "Failed pledge(2)");
#endif
while ((c = getopt(argc, argv, "RSc:eik:p:r:st:w")) != -1)
while ((c = getopt(argc, argv, "RSc:h:ik:p:r:st:w")) != -1)
switch (c) {
case 'R':
rawout = 1;
@ -55,8 +59,8 @@ main(int argc, char *argv[])
case 'c':
gemini.certfile = optarg;
break;
case 'e':
gemini.flags |= GEMINI_STRICT;
case 'h':
gemini.proxy = optarg;
break;
case 'i':
prompt = 1;
@ -157,10 +161,10 @@ err:
gemini_fini(&gemini);
return (EX_SOFTWARE);
usage:
(void)fprintf(stderr, "usage: gemini-cat [-RSeisw] "
"[-c cert-file] [-k key-file] [-p port]\n"
" [-r max-redirects] [-t tofu-file] "
"URL...\n");
(void)fprintf(stderr, "usage: gemini-cat [-RSisw] "
"[-c cert-file] [-h proxy-host] [-k key-file]\n"
" [-p port] [-r max-redirects] "
"[-t tofu-file] URL...\n");
return (EX_USAGE);
}

View File

@ -1,4 +1,4 @@
.Dd May 1, 2020
.Dd Jun 20, 2020
.Dt LIBGEMINICLIENT 3
.Os
.Sh NAME
@ -11,6 +11,8 @@
.Fn gemini_create void
.Ft void
.Fn gemini_init "struct gemini *ctx"
.Ft void
.Fn gemini_envinit "struct gemini *ctx"
.Ft int
.Fn gemini_connect "struct gemini *ctx" "const char *url"
.Ft int
@ -37,6 +39,39 @@ Initializes the provided
.Vt gemini
structure.
.Pp
.Fn gemini_envinit
Initializes the provided
.Vt gemini
structure, using the following environment variables,
if they are provided:
.Pp
.Bl -tag -width Ds -compact
.It Ev GEMINI_KEYFILE
Initializes the
.Fa keyfile
field.
.It Ev GEMINI_CERTFILE
Initializes the
.Fa certfile
field.
.It Ev GEMINI_TOFUFILE
Initializes the
.Fa tofufile
field.
.It Ev GEMINI_PROXY
Initializes the
.Fa proxy
field.
.It Ev GEMINI_MAX_REDIRECTS
Initializes the
.Fa maxredirects
field.
.It Ev GEMINI_PORT
Initializes the
.Fa port
field.
.El
.Pp
.Fn gemini_connect
opens a connection to the provided
.Fa url ,
@ -123,16 +158,17 @@ The useful members of the
structure are as follows:
.Bd -literal -offset indent -compact
struct gemini {
char meta[GEMINI_META_MAX + 1];
const char *certfile; /* Certificate file */
const char *keyfile; /* Key file */
const char *tofufile; /* Known-hosts file */
size_t metalen; /* Length of `meta' */
int flags; /* Configuration flags */
int maxredirects; /* Default 5 */
int socketfd; /* The socket to use */
short status; /* Response STATUS */
short port; /* Default port */
const char *certfile; /* Certificate file */
const char *keyfile; /* Key file */
const char *tofufile; /* Known-hosts file */
const char *proxy; /* Proxy host */
char *meta; /* Response META */
size_t metalen; /* Length of `meta' */
int flags; /* Configuration flags */
int maxredirects; /* Default 5 */
int socketfd; /* The socket to use */
short status; /* Response STATUS */
short port; /* Default port */
};
.Ed
.Pp
@ -145,8 +181,6 @@ Write any new certificate information to
.Fa tofufile
on
.Dn gemini_close .
.It Dv GEMINI_STRICT
Enforce a strict interpretation of the Gemini protocol.
.El
.Pp
All knows

View File

@ -1,6 +1,8 @@
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -21,8 +23,6 @@ enum {
GEMINI_STATE_INIT = 0,
GEMINI_STATE_REQUEST,
GEMINI_STATE_RESPONSE,
GEMINI_STATE_RESPONSE_WHITESPACE,
GEMINI_STATE_RESPONSE_META,
GEMINI_STATE_READ,
GEMINI_STATE_DONE,
GEMINI_STATE_ERROR
@ -32,6 +32,7 @@ enum {
#define GEMINI_DEFAULT_PORT 1965
#endif
#define ISASCIIBLANK(c) ((c) == '\t' || (c) == ' ')
#define ISASCIILOWER(c) ((c) >= 'a' && (c) <= 'z')
#define ISASCIIUPPER(c) ((c) >= 'A' && (c) <= 'Z')
#define ISASCIIDIGIT(c) ((c) >= '0' && (c) <= '9')
@ -65,8 +66,13 @@ gemini_init(struct gemini *g)
g->certmem = NULL;
g->keyfile = NULL;
g->certfile = NULL;
g->metalen = 0;
g->proxy = NULL;
g->meta = NULL;
g->extra = NULL;
g->reqlen = 0;
g->reslen = 0;
g->metalen = 0;
g->extralen = 0;
g->keylen = 0;
g->certlen = 0;
g->index = 0;
@ -81,6 +87,44 @@ gemini_init(struct gemini *g)
g->port = 0;
}
void
gemini_envinit(struct gemini *g)
{
const char *s;
char *p;
unsigned long ul;
gemini_init(g);
g->keyfile = getenv("GEMINI_KEYFILE");
g->certfile = getenv("GEMINI_CERTFILE");
g->certfile = getenv("GEMINI_TOFUFILE");
g->proxy = getenv("GEMINI_PROXY");
if ((s = getenv("GEMINI_MAX_REDIRECTS")) != NULL) {
errno = 0;
ul = strtoul(s, &p, 0);
if (errno == 0 && (p == s || *p != '\0'))
errno = EINVAL;
if (errno == 0 && ul > INT_MAX)
errno = ERANGE;
if (errno == 0)
g->maxredirects = ul;
else
warn("GEMINI_MAX_REDIRECTS");
}
if ((s = getenv("GEMINI_PORT")) != NULL) {
errno = 0;
ul = strtoul(s, &p, 0);
if (errno == 0 && (p == s || *p != '\0'))
errno = EINVAL;
if (errno == 0 && ul > SHRT_MAX)
errno = ERANGE;
if (errno == 0)
g->port = ul;
else
warn("GEMINI_PORT");
}
}
void
gemini_reset(struct gemini *g)
{
@ -95,8 +139,12 @@ gemini_reset(struct gemini *g)
}
g->tls = NULL;
g->tls_config = NULL;
g->metalen = 0;
g->meta = NULL;
g->extra = NULL;
g->reqlen = 0;
g->reslen = 0;
g->metalen = 0;
g->extralen = 0;
g->index = 0;
g->state = GEMINI_STATE_INIT;
g->status = 0;
@ -177,6 +225,12 @@ gemini_connect(struct gemini *g, const char *url)
errno = EINVAL;
return (-1);
}
/* Proxy */
if (g->proxy != NULL && strlcpy(g->request, g->proxy,
sizeof(g->request)) >= sizeof(g->request)) {
errno = EINVAL;
return (-1);
}
/* Initialize TLS */
if ((g->tls_config = tls_config_new()) == NULL)
return (-1);
@ -276,7 +330,6 @@ gemini_read(struct gemini *g, void *b, size_t bs)
size_t j;
int c;
cb = b;
if (g->state == GEMINI_STATE_ERROR)
return (-1);
if (g->state == GEMINI_STATE_DONE)
@ -289,113 +342,87 @@ gemini_read(struct gemini *g, void *b, size_t bs)
goto errt;
g->index += r;
}
g->status = 0;
g->reslen = 0;
g->index = 0;
g->status = 0;
g->state = GEMINI_STATE_RESPONSE;
}
for (;;) {
i = i != r ? i : 0;
if (i == 0 && (r = tls_read(g->tls, b, bs)) <= 0)
goto errt;
switch (g->state) {
case GEMINI_STATE_RESPONSE:
/* This is (probably) over-engineered. */
for (; i < r && g->index < 2; i++, g->index++) {
if (!ISASCIIDIGIT(cb[i]))
goto errinval;
g->status =
g->status * 10 + (cb[i] - '0');
}
if (g->index < 2)
break;
g->state = GEMINI_STATE_RESPONSE_WHITESPACE;
break;
case GEMINI_STATE_RESPONSE_WHITESPACE:
/* The unspecified maximum is kind-of absurd. */
for (; i < r && (cb[i] == ' ' || cb[i] == '\t');
if (g->state == GEMINI_STATE_RESPONSE) {
do {
if (g->reslen >= sizeof(g->response))
goto errinval;
if ((r = tls_read(g->tls,
g->response + g->reslen,
sizeof(g->response) - g->reslen)) <= 0)
goto errt;
for (i = g->reslen, g->reslen += r;
i < g->reslen && g->response[i] != '\n';
i++)
/* do nothing */;
if (i == r)
break;
g->metalen = 0;
g->state = GEMINI_STATE_RESPONSE_META;
} while (i == g->reslen);
g->extra = g->response + i + 1;
g->extralen = g->reslen - i - 1;
g->reslen = i - (g->response[i - 1] == '\r');
g->response[g->reslen] = '\0';
if (g->reslen < 2)
goto errinval;
if (!ISASCIIDIGIT(g->response[0]) ||
!ISASCIIDIGIT(g->response[1]))
goto errinval;
if (g->reslen > 2 && !ISASCIIBLANK(g->response[2]))
goto errinval;
g->meta = g->response + (g->reslen > 2 ? 3 : 2);
g->metalen = g->reslen > 2 ? g->reslen - 3 : 0;
g->status = (g->response[0] - '0') * 10 +
(g->response[1] - '0');
if (g->response[0] != '3')
g->redirects = 0;
if (g->response[0] != '2' && g->extralen > 0)
goto errinval;
switch (g->response[0]) {
case '0':
goto errinval;
case '1':
goto done;
case '2':
g->index = 0;
g->state = GEMINI_STATE_READ;
break;
case GEMINI_STATE_RESPONSE_META:
/*
* If the white-space had a reasonable maximum
* then this would be easier.
*/
for (j = i; j < r && cb[j] != '\n'; j++)
/* do nothing */;
if (g->metalen + j - i > sizeof(g->meta))
goto errinval;
(void)memcpy(g->meta + g->metalen, cb + i,
j - i);
g->metalen += j - i;
if (cb[j] != '\n') {
i = j;
break;
}
if (g->metalen > 0 &&
g->meta[g->metalen - 1] == '\r') {
g->metalen--;
} else if (g->flags & GEMINI_STRICT) {
if (g->metalen == GEMINI_META_MAX)
goto errinval;
g->meta[g->metalen++] = '\n';
i = j + 1;
break;
}
if (g->metalen > GEMINI_META_MAX)
goto errinval;
g->meta[g->metalen] = '\0';
i = j + 1;
if (g->status / 10 != 3)
g->redirects = 0;
switch (g->status / 10) {
case 0:
goto errinval;
case 1:
case '3':
if (g->maxredirects == 0)
goto done;
case 2:
g->state = GEMINI_STATE_READ;
break;
case 3:
if (g->maxredirects == 0)
goto done;
if (g->redirects >= g->maxredirects)
goto errredir;
g->redirects++;
if (gemini_connect_query(g, g->meta,
NULL) != 0)
goto err;
return (gemini_read(g, b, bs));
case 4:
goto errg;
case 5:
goto errg;
case 6:
goto done;
case 7:
goto errinval;
case 8:
goto errinval;
case 9:
goto errinval;
default:
abort(); /* unreachable */
}
break;
case GEMINI_STATE_READ:
if (i > 0) {
(void)memmove(cb, cb + i, r - i);
r -= i;
}
return (r);
if (g->redirects >= g->maxredirects)
goto errredir;
g->redirects++;
if (gemini_connect_query(g, g->meta, NULL) != 0)
goto err;
return (gemini_read(g, b, bs));
case '4':
goto errg;
case '5':
goto errg;
case '6':
goto done;
case '7':
goto errinval;
case '8':
goto errinval;
case '9':
goto errinval;
default:
abort(); /* unreachable */;
abort(); /* unreachable */
}
}
if (g->state != GEMINI_STATE_READ)
abort(); /* unreachable */
if (g->index < g->extralen) {
i = g->extralen - g->index;
i = i < bs ? i : bs;
(void)memcpy(b, g->extra + g->index, i);
g->index += i;
return (i);
}
return (tls_read(g->tls, b, bs));
eof:
if (g->state != GEMINI_STATE_READ) {
warnx("Unexpected EOF");

View File

@ -13,7 +13,6 @@
#define GEMINI_REDIRECT_MAX 5
#define GEMINI_TOFU_WRITE 0x01
#define GEMINI_STRICT 0x02
struct gemini_tofu {
struct gemini_tofu *next;
@ -24,10 +23,10 @@ struct gemini_tofu {
#define GEMINI_INITIALIZER \
{ { 0 }, { 0 }, NULL, NULL, NULL, NULL, NULL, NULL, \
NULL, NULL, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, \
GEMINI_REDIRECT_MAX, 0, 0 }
NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, \
-1, -1, 0, 0, 0, 0, GEMINI_REDIRECT_MAX, 0, 0 }
struct gemini {
char meta[GEMINI_META_MAX + 1];
char response[3 + GEMINI_META_MAX + 2];
char request[GEMINI_URL_MAX + 2];
struct tls *tls;
struct tls_config *tls_config;
@ -37,8 +36,13 @@ struct gemini {
const char *keyfile;
const char *certfile;
const char *tofufile;
size_t metalen;
const char *proxy;
const char *meta;
const char *extra;
size_t reqlen;
size_t reslen;
size_t metalen;
size_t extralen;
size_t keylen;
size_t certlen;
size_t index;
@ -55,6 +59,7 @@ struct gemini {
struct gemini *gemini_create(void);
void gemini_init(struct gemini *);
void gemini_envinit(struct gemini *);
void gemini_reset(struct gemini *);
int gemini_connect_query(struct gemini *, const char *,
const char *);