From 5dcddc70778c30460048ba320aaed42266a51d5c Mon Sep 17 00:00:00 2001 From: styan Date: Sat, 20 Jun 2020 04:56:34 +0000 Subject: [PATCH] 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. --- gemini-cat.1 | 46 ++++++++- gemini-cat.c | 18 ++-- libgeminiclient.3 | 60 +++++++++--- libgeminiclient.c | 231 ++++++++++++++++++++++++++-------------------- libgeminiclient.h | 15 ++- 5 files changed, 238 insertions(+), 132 deletions(-) diff --git a/gemini-cat.1 b/gemini-cat.1 index 178b245..a11769f 100644 --- a/gemini-cat.1 +++ b/gemini-cat.1 @@ -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 diff --git a/gemini-cat.c b/gemini-cat.c index 9c83beb..b7ccaa0 100644 --- a/gemini-cat.c +++ b/gemini-cat.c @@ -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); } diff --git a/libgeminiclient.3 b/libgeminiclient.3 index 27a7185..fe06dff 100644 --- a/libgeminiclient.3 +++ b/libgeminiclient.3 @@ -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 diff --git a/libgeminiclient.c b/libgeminiclient.c index 4297b94..4cc14ab 100644 --- a/libgeminiclient.c +++ b/libgeminiclient.c @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #include @@ -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"); diff --git a/libgeminiclient.h b/libgeminiclient.h index 296415c..ab09267 100644 --- a/libgeminiclient.h +++ b/libgeminiclient.h @@ -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 *);