~zethra/public-inbox

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
4 3

[PATCH stargazer] Add support for proxy protocol

Details
Message ID
<20240729033715.44087-1-ttocsneb@benjaminja.info>
DKIM signature
pass
Download raw message
Patch: +460 -309
Change *ip-log* to *log-ip* in docs to reflect the actual settings.
---
I have a special use case where I need to have support for the tcp proxy
protocol. I've added support for it and figure that I could send in a
patch for those who might also have a use case for this feature.

In this patch, before the tls handshake, the server will check for a
proxy protocol header. If it is found, then the remote_addr will be
overridden with the found source address. If no header is found, then no 
changes are made to the connection.

I also noticed that the docs have the setting `log-ip` misspelled as
`ip-log`, so that has been fixed as well. Also added -x option to col in
render-docs so tables look nice in any editor.

P.S.
This is the first time I have worked with patch emails, so please let me
know if there is anything that I have done wrong.

 Cargo.lock              |  67 ++++-
 Cargo.toml              |   1 +
 doc/stargazer-ini.scd   |  10 +-
 doc/stargazer.1.txt     |  40 +--
 doc/stargazer.ini.5.txt | 542 ++++++++++++++++++++--------------------
 scripts/render-docs     |   4 +-
 src/main.rs             | 105 +++++++-
 7 files changed, 460 insertions(+), 309 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index de04070..a3a88b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -41,7 +41,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
 "synstructure",
]

@@ -53,7 +53,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -271,6 +271,12 @@ dependencies = [
 "serde",
]

[[package]]
name = "bytes"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"

[[package]]
name = "cc"
version = "1.0.96"
@@ -430,7 +436,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -442,6 +448,12 @@ dependencies = [
 "const-random",
]

[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"

[[package]]
name = "errno"
version = "0.3.8"
@@ -884,6 +896,16 @@ dependencies = [
 "unicode-ident",
]

[[package]]
name = "proxy-protocol"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e50c72c21c738f5c5f350cc33640aee30bf7cd20f9d9da20ed41bce2671d532"
dependencies = [
 "bytes",
 "snafu",
]

[[package]]
name = "quote"
version = "1.0.36"
@@ -1067,7 +1089,7 @@ checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -1121,6 +1143,27 @@ dependencies = [
 "autocfg",
]

[[package]]
name = "snafu"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
dependencies = [
 "doc-comment",
 "snafu-derive",
]

[[package]]
name = "snafu-derive"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 1.0.109",
]

[[package]]
name = "socket2"
version = "0.4.10"
@@ -1174,6 +1217,7 @@ dependencies = [
 "mime_guess",
 "once_cell",
 "percent-encoding",
 "proxy-protocol",
 "rcgen",
 "regex",
 "rust-ini",
@@ -1194,6 +1238,17 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"

[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "syn"
version = "2.0.60"
@@ -1213,7 +1268,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
@@ -1233,7 +1288,7 @@ checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "syn 2.0.60",
]

[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 22779c3..8aeeb73 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -53,6 +53,7 @@ mime_guess = "2.0.4"
socket2 = "0.5.7"
percent-encoding = "2.3.1"
uriparse = "0.6"
proxy-protocol = "0.5.0"

anyhow = "1.0.82"
once_cell = "1.19.0"
diff --git a/doc/stargazer-ini.scd b/doc/stargazer-ini.scd
index f08b003..7a73de9 100644
--- a/doc/stargazer-ini.scd
+++ b/doc/stargazer-ini.scd
@@ -57,16 +57,16 @@ that *stargazer* will server requests to. At least one route must be specified.
	Whether or not to log connections to stdout. Disabling this may increase
	performance. On by default. Always on if the debug cli flag is on.

*ip-log*
*log-ip*
	Log client IP addresses in the connection log. Mutually exclusive with
	*ip-log-partial*. Off by default.
	*log-ip-partial*. Off by default.

*ip-log-partial*
*log-ip-partial*
	Log partial client IP addresses in the connection log. For IPv4 addresses
	only the first 2 octets are logged, the rest are zeroed. For IPv6 addresses
	only the first 3 16bit segments are logged, the rest are zeroed. May be
	preferable to *ip-log* to help maintain user privacy. Mutually exclusive
	with *ip-log-partial*. Off by default.
	preferable to *log-ip* to help maintain user privacy. Mutually exclusive
	with *log-ip-partial*. Off by default.

*request-timeout*
	Number of seconds to wait for the client to send a complete request.  If
diff --git a/doc/stargazer.1.txt b/doc/stargazer.1.txt
index d0003ba..37e7877 100644
--- a/doc/stargazer.1.txt
+++ b/doc/stargazer.1.txt
@@ -1,4 +1,4 @@
stargazer(1)		General Commands Manual		  stargazer(1)
stargazer(1)            General Commands Manual           stargazer(1)

NAME
       stargazer - a gemini server
@@ -8,29 +8,29 @@ SYNOPSIS

ARGS
       path
	   Specifies an alternate path to read the config file from.
           Specifies an alternate path to read the config file from.

OPTIONS
       -D --debug
	   Turn	 on  debug  logging.  This logs a lot more information
	   about the servers operation which is useful	for  debugging
	   problems.  This  is more verbose then is likely desired for
	   normal operation.
           Turn on debug logging. This logs a lot more information
           about the servers operation which is useful for debugging
           problems. This is more verbose then is likely desired for
           normal operation.
       -d --dev
	   Start the server in dev mode. Server will server files from
	   the current directory over ‘localhost:1965‘. This overrides
	   the config file and the -C option.
           Start the server in dev mode. Server will server files from
           the current directory over `localhost:1965`. This overrides
           the config file and the -C option.
       --check-config
	   Check for config errors and exit.
           Check for config errors and exit.
       -V --version
	   Print program version and exit.
           Print program version and exit.
       -h --help
	   Print command line argument help.
           Print command line argument help.

LOGGING
       Server errors and other debugging info is printed to stderr  in
       an  unspecified	format.	 Client	 request logging is printed to
       stdout, with each of the following fields in  order,  separated
       Server errors and other debugging info is printed to stderr in
       an unspecified format. Client request logging is printed to
       stdout, with each of the following fields in order, separated
       by a tab character:

       •   Service time in RFC 3339 format
@@ -45,9 +45,9 @@ SEE ALSO
       stargazer.ini(5) for configuration instructions.

AUTHORS
       Maintained  by Sashanoraa <sasha@noraa.gay>. Up-to-date sources
       can   be	  found	  at   https://sr.ht/˜zethra/stargazer/	   and
       bugs/patches	can	be     submitted     by	   email    to
       ˜zethra/stargazer@lists.sr.ht.
       Maintained by Sashanoraa <sasha@noraa.gay>. Up-to-date sources
       can be found at https://sr.ht/~zethra/stargazer/ and
       bugs/patches can be submitted by email to
       ~zethra/stargazer@lists.sr.ht.

			      1980-01-01		  stargazer(1)
                              2024-07-29                  stargazer(1)
diff --git a/doc/stargazer.ini.5.txt b/doc/stargazer.ini.5.txt
index f3bb559..05d25fc 100644
--- a/doc/stargazer.ini.5.txt
+++ b/doc/stargazer.ini.5.txt
@@ -1,303 +1,304 @@
stargazer.ini(5)	  File Formats Manual	      stargazer.ini(5)
stargazer.ini(5)          File Formats Manual         stargazer.ini(5)

NAME
       stargazer.ini - configuration file for stargazer(1)

EXAMPLE CONFIG
	   listen = 0.0.0.0
           listen = 0.0.0.0

	   [:tls]
	   store = /var/lib/gemini/certs
	   organiztion = example org
           [:tls]
           store = /var/lib/gemini/certs
           organiztion = example org

	   [example.com]
	   root = /srv/gemini/example.com
           [example.com]
           root = /srv/gemini/example.com

	   [example.com˜(.*).cgi(.*)]
	   root = /srv/gemini/example.com/cgi-bin
	   cgi = on
           [example.com~(.*).cgi(.*)]
           root = /srv/gemini/example.com/cgi-bin
           cgi = on

       An   default   config   file   should   also  be	 installed  at
       An default config file should also be installed at
       /etc/stargazer.ini

SYNTAX
       stargazer.ini is an INI file. Each line is either  a  key/value
       pair,  or  a  section heading. Key/value pairs are specified as
       stargazer.ini is an INI file. Each line is either a key/value
       pair, or a section heading. Key/value pairs are specified as
       key=value, and sections as [section]. Extra white space in gen‐
       erally ignored. Boolean values can be  written  as  true/false,
       erally ignored. Boolean values can be written as true/false,
       on/off, yes/no, and are case insensitive.

CONFIGURATION KEYS
       The  meaning  of the key depends on the section. Anonymous keys
       (prior to the first [section] directive) are  used  to  specify
       parameters  for	the daemon itself. Sections whose name is pre‐
       fixed with ":", e.g. [:tls], are sub-categories of  the	daemon
       configuration.  Otherwise, section names refer to the hostnames
       The meaning of the key depends on the section. Anonymous keys
       (prior to the first [section] directive) are used to specify
       parameters for the daemon itself. Sections whose name is pre‐
       fixed with ":", e.g. [:tls], are sub-categories of the daemon
       configuration. Otherwise, section names refer to the hostnames
       of domains serviced by the stargazer daemon. Each of these sec‐
       tions is a route that stargazer will  server  requests  to.  At
       tions is a route that stargazer will server requests to. At
       least one route must be specified.

   ANONYMOUS KEYS
       listen
	   A  space-separated  list of addresses that the daemon shall
	   bind to. Each address shall take the	 format	 address:port.
	   If :port is omitted, 1965 (the default Gemini port) is pre‐
	   sumed.  To  specify an IPv6 address, enclose it in [], e.g.
	   [::]. Note that IPv6 listener always have IPV6_V6ONLY  set,
	   so they will only listen on the IPv6 interface and not also
	   IPv4.  If  you  wish	 to  listen  on	 both,	specify	 both.
	   Stargazer supports listening on  multiple  interfaces/ports
	   but does not support serving different content on different
	   interfaces/ports. It does support serving different content
	   on different domains and paths.
           A space-separated list of addresses that the daemon shall
           bind to. Each address shall take the format address:port.
           If :port is omitted, 1965 (the default Gemini port) is pre‐
           sumed. To specify an IPv6 address, enclose it in [], e.g.
           [::]. Note that IPv6 listener always have IPV6_V6ONLY set,
           so they will only listen on the IPv6 interface and not also
           IPv4. If you wish to listen on both, specify both.
           Stargazer supports listening on multiple interfaces/ports
           but does not support serving different content on different
           interfaces/ports. It does support serving different content
           on different domains and paths.

       connection-logging
	   Whether or not to log connections to stdout. Disabling this
	   may	increase  performance. On by default. Always on if the
	   debug cli flag is on.

       ip-log
	   Log client IP addresses in the connection log. Mutually ex‐
	   clusive with ip-log-partial. Off by default.

       ip-log-partial
	   Log partial client IP addresses in the connection log.  For
	   IPv4 addresses only the first 2 octets are logged, the rest
	   are	zeroed. For IPv6 addresses only the first 3 16bit seg‐
	   ments are logged, the rest are zeroed. May be preferable to
	   ip-log to help maintain user	 privacy.  Mutually  exclusive
	   with ip-log-partial. Off by default.
           Whether or not to log connections to stdout. Disabling this
           may increase performance. On by default. Always on if the
           debug cli flag is on.

       log-ip
           Log client IP addresses in the connection log. Mutually ex‐
           clusive with log-ip-partial. Off by default.

       log-ip-partial
           Log partial client IP addresses in the connection log. For
           IPv4 addresses only the first 2 octets are logged, the rest
           are zeroed. For IPv6 addresses only the first 3 16bit seg‐
           ments are logged, the rest are zeroed. May be preferable to
           log-ip to help maintain user privacy. Mutually exclusive
           with log-ip-partial. Off by default.

       request-timeout
	   Number of seconds to wait for the client to send a complete
	   request.  If the timeout is exceeded the timeout, stargazer
	   will	 send ‘59 Request timeout‘ and close the connection. 5
	   seconds by default. Set to 0 to disable.
           Number of seconds to wait for the client to send a complete
           request.  If the timeout is exceeded the timeout, stargazer
           will send `59 Request timeout` and close the connection. 5
           seconds by default. Set to 0 to disable.

       response-timeout
	   Number of seconds to wait for the client to send a complete
	   request and for stargazer to finish sending	the  response.
	   If the timeout is exceeded the timeout, stargazer will send
	   and	close  the  connection.	 Disabled  by default default.
	   Warning, if this is set, large files and cgi scripts may be
	   cut off before their response finishes.
           Number of seconds to wait for the client to send a complete
           request and for stargazer to finish sending the response.
           If the timeout is exceeded the timeout, stargazer will send
           and close the connection. Disabled by default default.
           Warning, if this is set, large files and cgi scripts may be
           cut off before their response finishes.

   TLS KEYS
       The following keys are accepted under the [:tls] section:

       store
	   Path to the certificate store on disk.  This	 should	 be  a
	   persistent  directory  writable  by	the daemon. The daemon
	   manages its own certificates - no user intervention is  re‐
	   quired,  except in the case of moving the daemon to another
	   host, in which case the certificate store must be copied to
	   the new host.
           Path to the certificate store on disk. This should be a
           persistent directory writable by the daemon. The daemon
           manages its own certificates - no user intervention is re‐
           quired, except in the case of moving the daemon to another
           host, in which case the certificate store must be copied to
           the new host.

       organization
	   An optional key used during	certificate  generation.  Fill
	   this	 in  with the name of the organization responsible for
	   the host and it will be filled in as the X.509 /O name.
           An optional key used during certificate generation. Fill
           this in with the name of the organization responsible for
           the host and it will be filled in as the X.509 /O name.

       gen-certs
	   Set to false to turn off automatic certificate generation.
           Set to false to turn off automatic certificate generation.

       regen-certs
	   Set to false to turn off automatic regeneration of  expired
	   certificates.
           Set to false to turn off automatic regeneration of expired
           certificates.

       cert-lifetime
	   Set	how  long  auto-generated  certs last before expiring.
	   Specified as a number followed by y for a number of	years,
	   m  for a number of months (a month being 30 days), or d for
	   a number days. Ex. 5y for 5 years or 30d for 30 days.
           Set how long auto-generated certs last before expiring.
           Specified as a number followed by y for a number of years,
           m for a number of months (a month being 30 days), or d for
           a number days. Ex. 5y for 5 years or 30d for 30 days.

   ROUTING KEYS
       To configure stargazer to service requests, routing  keys  must
       be  defined.  The  name of the configuration section is used to
       To configure stargazer to service requests, routing keys must
       be defined. The name of the configuration section is used to
       determine what kinds of requests it configures.

       The format of the section name is the hostname to be  serviced,
       followed	 by  a token which defines the routing strategy, and a
       string whose format is specific to each routing	strategy.  The
       token  and match string may be omitted (i.e. [hostname] alone),
       The format of the section name is the hostname to be serviced,
       followed by a token which defines the routing strategy, and a
       string whose format is specific to each routing strategy. The
       token and match string may be omitted (i.e. [hostname] alone),
       which implies path routing against "/". A port may not be spec‐
       ified along with the hostname. Stargazer does not support serv‐
       ing different content on different ports.

       :   Route  by  path  prefix.  The  URL  path  is	 compared   to
	   "string/".
       :   Route by path prefix. The URL path is compared to
           "string/".
       =   Exact match. The URL path must exactly match the string.
       ˜   Regular expression routing. The string is a Rust-compatible
	   regular expression which is tested against the URL path.

       See  the	 docs for the syntax section of the docs for the regex
       rust crate for a definition of the  regular  expression	syntax
       ~   Regular expression routing. The string is a Rust-compatible
           regular expression which is tested against the URL path.

       See the docs for the syntax section of the docs for the regex
       rust crate for a definition of the regular expression syntax
       and features:

       https://docs.rs/regex/1.4.2/regex/#syntax

       Some example section names and examples of matching paths:

       [example.org:/foo]	/foo, /foo/bar, /foo/bar/baz
       [example.org=/foo.txt]	/foo.txt
       [example.org˜/[a-	/foo.png, /bar.webp
       [example.org:/foo]       /foo, /foo/bar, /foo/bar/baz
       [example.org=/foo.txt]   /foo.txt
       [example.org~/[a-        /foo.png, /bar.webp
       z]+\.(png|jpg|webp)]

       Routes  should  be  ordered  from  least	 to most specific. The
       Routes should be ordered from least to most specific. The
       matching algorithm attempts to match the URL against each route
       in reverse order, and chooses the first route which matches.

       Within each routing section, the following  keys	 are  used  to
       Within each routing section, the following keys are used to
       configure how stargazer will respond to matching requests:

       root
	   Configures  the  path  on  disk  from  which files shall be
	   served for this host. The path component of the URL will be
	   appended to this value to form the path to files on disk to
	   serve.
           Configures the path on disk from which files shall be
           served for this host. The path component of the URL will be
           appended to this value to form the path to files on disk to
           serve.

	   If example.org/foo/bar.txt is requested,  and  a  route  is
	   configured  for  [example.org:/foo]	with  the  root set to
	   /srv/gemini, /srv/gemini/foo/bar.txt will be served.
           If example.org/foo/bar.txt is requested, and a route is
           configured for [example.org:/foo] with the root set to
           /srv/gemini, /srv/gemini/foo/bar.txt will be served.

       rewrite
	   If regular expression routing is used, the  rewrite	direc‐
	   tive	 may  be used to rewrite the URL path component before
	   proceeding. The URL will be set to the value of the rewrite
	   expression. If \N appears in the rewrite value, where N  is
	   a number, that capture group will be substituted for \N. If
	   \{name}  appears,  where  name is a named capture group, it
	   will be substituted. Capture group 0 refers to  the	entire
	   matched path.
           If regular expression routing is used, the rewrite direc‐
           tive may be used to rewrite the URL path component before
           proceeding. The URL will be set to the value of the rewrite
           expression. If \N appears in the rewrite value, where N is
           a number, that capture group will be substituted for \N. If
           \{name} appears, where name is a named capture group, it
           will be substituted. Capture group 0 refers to the entire
           matched path.

	   Example:
           Example:

	       [localhost˜ˆ/([a-zA-Z]+)\.(?P<extension>png|jpg)$]
	       root=./root
	       rewrite=/images/\1.\{extension}
               [localhost~^/([a-zA-Z]+)\.(?P<extension>png|jpg)$]
               root=./root
               rewrite=/images/\1.\{extension}

	   This will rewrite a request for /example.png to /images/ex‐
	   ample.png.  This  directive	will also modify the path of a
	   redirect URL if used in a redirect route.
           This will rewrite a request for /example.png to /images/ex‐
           ample.png. This directive will also modify the path of a
           redirect URL if used in a redirect route.

       index
	   Configures the name of the index file which shall be served
	   in the event that a request for this host does not  include
	   the filename part. Defaults to "index.gmi".
           Configures the name of the index file which shall be served
           in the event that a request for this host does not include
           the filename part. Defaults to "index.gmi".

       auto-index
	   "on"	 to  enable  the  auto-index  feature,	which presents
	   clients with a list of files	 in  the  requested  directory
	   when	 an  index  file cannot be found. Off by default. Only
	   availible for static file routes.
           "on" to enable the auto-index feature, which presents
           clients with a list of files in the requested directory
           when an index file cannot be found. Off by default. Only
           availible for static file routes.

       lang
	   Set this value as  the  lang	 parameter  for	 gemini	 files
	   served  under  this	route. The lang param will only be set
	   for text/gemini responses. The  Gemini  Specification  says
	   that	 the  lang  parameter should contain a comma separated
	   list of language identifier from RFC 4646.  Stargazer  will
	   use the lang parameter as given in this config. It is up to
	   the	user  to  provid a valid list of languages. Currently,
	   this parameter is only used when serving static files.
           Set this value as the lang parameter for gemini files
           served under this route. The lang param will only be set
           for text/gemini responses. The Gemini Specification says
           that the lang parameter should contain a comma separated
           list of language identifier from RFC 4646. Stargazer will
           use the lang parameter as given in this config. It is up to
           the user to provid a valid list of languages. Currently,
           this parameter is only used when serving static files.

       charset
	   Set this value as the  charset  parameter  for  text	 files
	   served  under  this	route. The lang param will only be set
	   for text/* responses. If the lang parameter	is  also  set,
	   then	 lang  will  be set instead of charset for text/gemini
	   responses. The Gemini Specification says that  the  charset
	   parameter  should be a character set specified in RFC 2046.
	   Stargazer will use the charset parameter as given  in  this
	   config.  It	is  up	to  the user to provid a valid charset
	   value. Currently, this parameter is only used when  serving
	   static files.
           Set this value as the charset parameter for text files
           served under this route. The lang param will only be set
           for text/* responses. If the lang parameter is also set,
           then lang will be set instead of charset for text/gemini
           responses. The Gemini Specification says that the charset
           parameter should be a character set specified in RFC 2046.
           Stargazer will use the charset parameter as given in this
           config. It is up to the user to provid a valid charset
           value. Currently, this parameter is only used when serving
           static files.

       mime-override
	   Override the MIME type of all files served under this route
	   with	 the  specified value. This parameter can only be used
	   when serving static files. This can be used with  an	 exact
	   match route to override the MIME type for a single file.
           Override the MIME type of all files served under this route
           with the specified value. This parameter can only be used
           when serving static files. This can be used with an exact
           match route to override the MIME type for a single file.

       redirect
	   Send	 a  redirect to this URI instead of serving other con‐
	   tent. The URI exactly  as  written  will  be	 sent  to  the
	   client. Mutually exclusive with most other settings.
           Send a redirect to this URI instead of serving other con‐
           tent. The URI exactly as written will be sent to the
           client. Mutually exclusive with most other settings.

       permanent
	   Send	 a permanent redirect instead of a temporary redirect.
	   Requires redirect to be set.
           Send a permanent redirect instead of a temporary redirect.
           Requires redirect to be set.

       cgi
	   "on" to enable CGI support. root must also  be  configured.
	   See	"CGI  Support"	for  details.  Mutually exclusive with
	   auto-index.
           "on" to enable CGI support. root must also be configured.
           See "CGI Support" for details. Mutually exclusive with
           auto-index.

       cgi-user
	   Run CGI process as this user and their primary  group.  Set
	   to  the  name of the user. Otherwise process is run as same
	   user as stargazer, which can be a security issue if the CGI
	   process  is	untrusted  (not	 that  running	untrusted  CGI
	   processes is a good idea to begin with). The user stargazer
	   is  run  as must be able to setuid the child process to the
	   user specified.
           Run CGI process as this user and their primary group. Set
           to the name of the user. Otherwise process is run as same
           user as stargazer, which can be a security issue if the CGI
           process is untrusted (not that running untrusted CGI pro‐
           cesses is a good idea to begin with). The user stargazer is
           run as must be able to setuid the child process to the user
           specified.

       scgi
	   "on" to enable  SCGI	 support.  scg-address	must  also  be
	   set.See  "SCI  Support" for details. root, index, and auto-
	   index not allowed when scgi is on.
           "on" to enable SCGI support. scg-address must also be
           set.See "SCI Support" for details. root, index, and auto-
           index not allowed when scgi is on.

       scgi-address
	   The address of the SCGI server to connect to. If this is  a
	   valid  file system path that exists, stargazer will connect
	   to a Unix domain socket at that path to send requests. Oth‐
	   erwise the address value will be treated as	a  socket  ad‐
	   dress and requests will be made over TCP.
           The address of the SCGI server to connect to. If this is a
           valid file system path that exists, stargazer will connect
           to a Unix domain socket at that path to send requests. Oth‐
           erwise the address value will be treated as a socket ad‐
           dress and requests will be made over TCP.

       cgi-timeout
	   Maximum  amount of time a CGI/SCGI process on this route is
	   allowed to run. If the timeout is exceeded, stargazer  will
	   send	 a  CGI	 error	to  the client and either kill the CGI
	   process or close the SCGI connection.  Values  is  a	 whole
	   number of seconds. By default CGI processes have no timeout
	   and stargazer will wait indefinitely for them to finish.
           Maximum amount of time a CGI/SCGI process on this route is
           allowed to run. If the timeout is exceeded, stargazer will
           send a CGI error to the client and either kill the CGI
           process or close the SCGI connection. Values is a whole
           number of seconds. By default CGI processes have no timeout
           and stargazer will wait indefinitely for them to finish.

       cert-path
	   Path	 to this certificate for this route, overrides the de‐
	   fault store path. This is intended to be used for certs not
	   managed by stargazer. As such stargazer will not attempt to
	   generate or regenerate these certs. If this	is  set,  key-
	   path must also be set.
           Path to this certificate for this route, overrides the de‐
           fault store path. This is intended to be used for certs not
           managed by stargazer. As such stargazer will not attempt to
           generate or regenerate these certs. If this is set, key-
           path must also be set.

       key-path
	   Path	 to  the  key that goes with the cert set by the cert-
	   path. If this is set, cert-path must also be set.
           Path to the key that goes with the cert set by the cert-
           path. If this is set, cert-path must also be set.

       client-cert
	   Path to a client certificate that the client	 must  use  to
	   access this route. Only one client certificate may be spec‐
	   ified per route. Client certificate must be in the PEM for‐
	   mat.
           Path to a client certificate that the client must use to
           access this route. Only one client certificate may be spec‐
           ified per route. Client certificate must be in the PEM for‐
           mat.

CGI Support
       stargazer  supports  a  limited version of CGI, compatible with
       stargazer supports a limited version of CGI, compatible with
       the Jetforce server. It is not a faithful implementation of RFC
       3875, but is  sufficient	 for  most  of	the  needs  of	Gemini
       3875, but is sufficient for most of the needs of Gemini
       servers.

       Set  cgi=on  for	 a  route configuration to enable CGI for that
       Set cgi=on for a route configuration to enable CGI for that
       route and set root to the path where the CGI scripts are found.
       If a client requests a script, it will be  executed,  and  must
       print  a	 Gemini	 response  (including status code and meta) to
       If a client requests a script, it will be executed, and must
       print a Gemini response (including status code and meta) to
       stdout.

       If a CGI process exceeded the timeout set by cgi-timeout or  if
       connection  to client is lost and the CGI process is still run‐
       ning it will be killed. On *nix systems, the CGI	 process  will
       receive	a  SIGTERM.  If it is still running after 3 seconds it
       If a CGI process exceeded the timeout set by cgi-timeout or if
       connection to client is lost and the CGI process is still run‐
       ning it will be killed. On *nix systems, the CGI process will
       receive a SIGTERM. If it is still running after 3 seconds it
       will receive a SIGKILL. On other systems (i.e. Windows) the CGI
       process will  just  be  killed.	If  cgi-timeout	 is  exceeded,
       stargazer  will	send  a CGI error message to the gemini client
       process will just be killed. If cgi-timeout is exceeded,
       stargazer will send a CGI error message to the gemini client
       before closing the connection.

       When stargazer exits, any CGI processes that are currently run‐
@@ -305,104 +306,97 @@ CGI Support

       The following environment variables will be set:

       ┌────────────────────┬──────────────────────┬─────────────────┐
       │ Variable	    │ Example		   │ Description     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ GATEWAY_INTERFACE  │ CGI/1.1		   │ CGI version     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ SERVER_PROTOCOL    │ GEMINI		   │ The server pro‐ │
       │		    │			   │ tocol	     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ SERVER_SOFTWARE    │ stargazer/0.1.0	   │ The   stargazer │
       │		    │			   │ server name and │
       │		    │			   │ version	     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ GEMINI_URL	    │ See [1]		   │ The   URL	 re‐ │
       │		    │			   │ quested  by the │
       │		    │			   │ client	     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ SCRIPT_NAME	    │ /cgi-bin/foo.sh	   │ The portion  of │
       │		    │			   │ the  URL refer‐ │
       │		    │			   │ ring   to	 the │
       │		    │			   │ script name.    │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ PATH_INFO	    │ /bar		   │ The   remainder │
       │		    │			   │ of	  the	path │
       │		    │			   │ following	     │
       │		    │			   │ SCRIPT_NAME.    │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ QUERY_STRING	    │ hello=world	   │ The       query │
       │		    │			   │ string  portion │
       │		    │			   │ of the URL. [3] │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ SERVER_NAME,	    │ example.org	   │ The server host │
       │ HOSTNAME	    │			   │ name.	     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ SERVER_PORT	    │ 1965		   │ The server port │
       │		    │			   │ number.	     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ REMOTE_HOST,  RE‐  │ 10.10.0.2		   │ The  clients IP │
       │ MOTE_ADDR	    │			   │ address.	     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ TLS_CLIENT_HASH    │ See [2]		   │ A	 fingerprint │
       │		    │			   │ that   can	  be │
       │		    │			   │ used	  to │
       │		    │			   │ uniquely  iden‐ │
       │		    │			   │ tify the cert.  │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ TLS_CLIENT_NOT_BE‐ │ 2020-10-22T04:31:40Z │ The  date	 the │
       │ FORE		    │			   │ client's	cert │
       │		    │			   │ is	 not   valid │
       │		    │			   │ before.	 See │
       │		    │			   │ [4].	     │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ TLS_CLIENT_NOT_AF‐ │ 2021-10-22T04:31:59Z │ The  date	 the │
       │ TER		    │			   │ client's	cert │
       │		    │			   │ is	 not   valid │
       │		    │			   │ after. See [4]. │
       ├────────────────────┼──────────────────────┼─────────────────┤
       │ REMOTE_USER	    │ "My cert"		   │ The      client │
       │		    │			   │ cert's   issuer │
       │		    │			   │ field.	     │
       └────────────────────┴──────────────────────┴─────────────────┘

       ┌───────────────────┬──────────────────────┬───────────────────┐
       │Variable           │ Example              │ Description       │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │GATEWAY_INTERFACE  │ CGI/1.1              │ CGI version       │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │SERVER_PROTOCOL    │ GEMINI               │ The server proto‐ │
       │                   │                      │ col               │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │SERVER_SOFTWARE    │ stargazer/0.1.0      │ The stargazer     │
       │                   │                      │ server name and   │
       │                   │                      │ version           │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │GEMINI_URL         │ See [1]              │ The URL requested │
       │                   │                      │ by the client     │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │SCRIPT_NAME        │ /cgi-bin/foo.sh      │ The portion of    │
       │                   │                      │ the URL referring │
       │                   │                      │ to the script     │
       │                   │                      │ name.             │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │PATH_INFO          │ /bar                 │ The remainder of  │
       │                   │                      │ the path follow‐  │
       │                   │                      │ ing SCRIPT_NAME.  │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │QUERY_STRING       │ hello=world          │ The query string  │
       │                   │                      │ portion of the    │
       │                   │                      │ URL. [3]          │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │SERVER_NAME,       │ example.org          │ The server host   │
       │HOSTNAME           │                      │ name.             │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │SERVER_PORT        │ 1965                 │ The server port   │
       │                   │                      │ number.           │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │REMOTE_HOST, RE‐   │ 10.10.0.2            │ The clients IP    │
       │MOTE_ADDR          │                      │ address.          │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │TLS_CLIENT_HASH    │ See [2]              │ A fingerprint     │
       │                   │                      │ that can be used  │
       │                   │                      │ to uniquely iden‐ │
       │                   │                      │ tify the cert.    │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │TLS_CLIENT_NOT_BE‐ │ 2020-10-22T04:31:40Z │ The date the      │
       │FORE               │                      │ client's cert is  │
       │                   │                      │ not valid before. │
       │                   │                      │ See [4].          │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │TLS_CLIENT_NOT_AF‐ │ 2021-10-22T04:31:59Z │ The date the      │
       │TER                │                      │ client's cert is  │
       │                   │                      │ not valid after.  │
       │                   │                      │ See [4].          │
       ├───────────────────┼──────────────────────┼───────────────────┤
       │REMOTE_USER        │ "My cert"            │ The client cert's │
       │                   │                      │ issuer field.     │
       └───────────────────┴──────────────────────┴───────────────────┘
       [1]: gemini://example.org/cgi-bin/foo.sh/bar?hello=world
       [2]: 1flC87yanv8KQ027TrvOOP/kA5yTWn3xLPJ+GxgaNB4=
       [3]:  stargazer will attempt to percent decode the query string
       as UTF-8. If this fails the string will be provided as is,  un‐
       [3]: stargazer will attempt to percent decode the query string
       as UTF-8. If this fails the string will be provided as is, un‐
       decoded. [4]: Timestamps are rfc3339 complaint, exact format is
       unspecified.

       The exit status of the script is ignored.

SCGI Support
       stargazer  has  SCGI  support  similar to its CGI support.  Set
       stargazer has SCGI support similar to its CGI support.  Set
       scgi=on for a route configuration to enable SCGI for that route
       and set scgi-address to the address of the SCGI server. The ad‐
       dress can either be a path to an Unix Domain Socket or the  TCP
       Socket  address. The headers included in the SCGI request match
       dress can either be a path to an Unix Domain Socket or the TCP
       Socket address. The headers included in the SCGI request match
       the CGI parameters list above with a few exceptions.

       If the SCGI server doesn't finish responding before cgi-timeout
       is exceeded (assuming one is set) stargazer will close the con‐
       nection to the SCGI server and send a CGI error to  the	gemini
       nection to the SCGI server and send a CGI error to the gemini
       client.

       If  the	connection to the gemini client is lost stargazer will
       If the connection to the gemini client is lost stargazer will
       close the connection to the SCGI server.

       New headers:

       ┌────────────────┬─────────┬──────────────────────────────────┐
       │ Variable	│ Example │ Description			     │
       ├────────────────┼─────────┼──────────────────────────────────┤
       │ CONTENT_LENGTH │ 0	  │ Length in bytes of	the  request │
       │		│	  │ body.  See [1].		     │
       ├────────────────┼─────────┼──────────────────────────────────┤
       │ SCGI		│ 1	  │ SCGI Version		     │
       └────────────────┴─────────┴──────────────────────────────────┘

       [1]:  Aways  0 since gemini doesn't have request bodies but in‐
       ┌───────────────┬─────────┬────────────────────────────────────┐
       │Variable       │ Example │ Description                        │
       ├───────────────┼─────────┼────────────────────────────────────┤
       │CONTENT_LENGTH │ 0       │ Length in bytes of the request     │
       │               │         │ body.  See [1].                    │
       ├───────────────┼─────────┼────────────────────────────────────┤
       │SCGI           │ 1       │ SCGI Version                       │
       └───────────────┴─────────┴────────────────────────────────────┘
       [1]: Aways 0 since gemini doesn't have request bodies but in‐
       cluded as it's required by the SCGI protocol.

       CGI header that are omitted from SCGI:
@@ -410,9 +404,9 @@ SCGI Support
       •   GATEWAY_INTERFACE

AUTHORS
       Maintained by Sashanoraa <sasha@noraa.gay>. Up-to-date  sources
       can    be   found   at	https://sr.ht/˜zethra/stargazer/   and
       bugs/patches    can    be     submitted	   by	  email	    to
       ˜zethra/stargazer@lists.sr.ht.
       Maintained by Sashanoraa <sasha@noraa.gay>. Up-to-date sources
       can be found at https://sr.ht/~zethra/stargazer/ and
       bugs/patches can be submitted by email to
       ~zethra/stargazer@lists.sr.ht.

			      1980-01-01	      stargazer.ini(5)
                              2024-07-29              stargazer.ini(5)
diff --git a/scripts/render-docs b/scripts/render-docs
index dd337fb..517ef38 100755
--- a/scripts/render-docs
+++ b/scripts/render-docs
@@ -1,4 +1,4 @@
#!/bin/sh

scdoc < doc/stargazer.scd | MANWIDTH=72 man -l - | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | col -b > doc/stargazer.1.txt
scdoc < doc/stargazer-ini.scd | MANWIDTH=72 man -l - | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | col -b > doc/stargazer.ini.5.txt
scdoc < doc/stargazer.scd | MANWIDTH=72 man -l - | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | col -bx > doc/stargazer.1.txt
scdoc < doc/stargazer-ini.scd | MANWIDTH=72 man -l - | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | col -bx > doc/stargazer.ini.5.txt
diff --git a/src/main.rs b/src/main.rs
index b0fa9ba..6049cce 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -30,6 +30,7 @@ use async_executor::Executor;
use async_io::Timer;
use async_net::{SocketAddrV6, TcpListener, TcpStream};
use cgi::{serve_cgi, serve_scgi};
use core::slice;
use futures_lite::*;
use futures_rustls::{server::TlsStream, TlsAcceptor};
use get_file::get_file;
@@ -234,14 +235,114 @@ async fn exit_on_sig() -> Result<()> {
    Ok(future::pending::<()>().await)
}

async fn parse_proxy_proto(
    stream: &mut TcpStream,
) -> anyhow::Result<Option<proxy_protocol::ProxyHeader>> {
    // proxy-protocol doesn't support async streams, so in order to parse the
    // proxy protocol, we need to pre-read the header before passing it in
    // without reading into tls data.
    //
    // First, check if there is a proxy protocol header with the first 12 bytes.
    // Second, read the whole proxy protocol header.
    // Third, pass the raw data into proxy-protocol to get parsed.

    let mut header = [0u8; 12];
    let n = stream.peek(&mut header).await?;
    let header = &header[..n];

    if header.starts_with(b"PROXY ") {
        // V1 - Read until \r\n
        let mut buf = Vec::<u8>::with_capacity(n);
        loop {
            // There might be a better way than reading 1 byte at a time
            let mut b = 0u8;
            stream.read_exact(slice::from_mut(&mut b)).await?;
            buf.push(b);
            if b == b'\n' {
                break;
            }
        }

        let mut buf = buf.as_slice();

        Ok(Some(proxy_protocol::parse(&mut buf)?))
    } else if header.starts_with(&[
        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
    ]) {
        // TODO this is untested. It should work, but I should make sure that
        // the correct number of bytes are being read.

        // V2 - Read header length bytes after the first 16 at [14,15]
        let mut header = [0u8; 16];
        stream.read_exact(&mut header).await?;
        let len = u16::from_be_bytes([header[14], header[15]]);

        let mut buf = vec![0u8; 16 + len as usize];
        let buf_slice = buf.as_mut_slice();
        header
            .into_iter()
            .enumerate()
            .for_each(|(i, c)| buf_slice[i] = c);
        stream.read_exact(&mut buf_slice[16..]).await?;
        let mut buf = buf.as_slice();

        Ok(Some(proxy_protocol::parse(&mut buf)?))
    } else {
        // There is no proxy protocol header, so do nothing
        Ok(None)
    }
}

// This is a separate function for ease of error handling
async fn start_task(
    stream: TcpStream,
    mut stream: TcpStream,
    acceptor: TlsAcceptor,
    remote_addr: SocketAddr,
    mut remote_addr: SocketAddr,
    server_port: u16,
) {
    use anyhow::Context;

    // Parse the proxy protocol header. If there is one, then replace the
    // remote_addr with the proxied source addr
    match parse_proxy_proto(&mut stream)
        .await
        .context("Error reading proxy protocol")
    {
        Ok(Some(proxy_protocol::ProxyHeader::Version1 { addresses })) => {
            match addresses {
                proxy_protocol::version1::ProxyAddresses::Unknown => {}
                proxy_protocol::version1::ProxyAddresses::Ipv4 {
                    source,
                    destination: _,
                } => remote_addr = SocketAddr::V4(source),
                proxy_protocol::version1::ProxyAddresses::Ipv6 {
                    source,
                    destination: _,
                } => remote_addr = SocketAddr::V6(source),
            }
        }
        Ok(Some(proxy_protocol::ProxyHeader::Version2 {
            command: _,
            transport_protocol: _,
            addresses,
        })) => match addresses {
            proxy_protocol::version2::ProxyAddresses::Ipv4 {
                source,
                destination: _,
            } => remote_addr = SocketAddr::V4(source),
            proxy_protocol::version2::ProxyAddresses::Ipv6 {
                source,
                destination: _,
            } => remote_addr = SocketAddr::V6(source),
            _ => {}
        },
        Ok(_) => {}
        Err(e) => {
            debug!("{:#}", e);
            return;
        }
    };

    // Accept tsl connection
    match acceptor
        .accept(stream)
-- 
2.39.3 (Apple Git-146)

[stargazer/patches] build failed

builds.sr.ht <builds@sr.ht>
Details
Message ID
<D31PKBICIM4C.2R7XOIY1OCO3S@fra01>
In-Reply-To
<20240729033715.44087-1-ttocsneb@benjaminja.info> (view parent)
DKIM signature
missing
Download raw message
stargazer/patches: FAILED in 24s

[Add support for proxy protocol][0] from [Benjamin Jacobs][1]

[0]: https://lists.sr.ht/~zethra/public-inbox/patches/54150
[1]: ttocsneb@benjaminja.info

✗ #1288076 FAILED stargazer/patches/freebsd.yml https://builds.sr.ht/~zethra/job/1288076
✗ #1288077 FAILED stargazer/patches/linux.yml   https://builds.sr.ht/~zethra/job/1288077
Details
Message ID
<c1b698b5-7179-4748-9c62-65089f12df63@noraa.gay>
In-Reply-To
<20240729033715.44087-1-ttocsneb@benjaminja.info> (view parent)
DKIM signature
pass
Download raw message
Hello, thanks for the patch!

It appears it failed to apply in CI. Could you make sure your patch it based off of the latest commit on the main branch? Do a git rebase if necessary. Also, would you be able to split this into multiple commits? With the typo fix and formatting change being their own commits. I can review the change in more depth in a few days.

Out of curiosity, what is your use case for this? I'm not familiar with this protocol.
Details
Message ID
<447BD553-1D2B-47B4-B30B-428A6BBB2786@benjaminja.info>
In-Reply-To
<c1b698b5-7179-4748-9c62-65089f12df63@noraa.gay> (view parent)
DKIM signature
pass
Download raw message
Whoops, the original message got sent as a new post and not as a reply.
Sorry about that.

> It appears it failed to apply in CI. Could you make sure your patch it 
> based off of the latest commit on the main branch? Do a git rebase if 
> necessary. Also, would you be able to split this into multiple commits? 
> With the typo fix and formatting change being their own commits. I can 
> review the change in more depth in a few days.

For sure, I'll split up the patch and see what I've messed up with CI.
Should the typo fix and formatting change both be in the same patch with
different commits or have a total of 3 patches: typo, formatting, and
proxy?

> Out of curiosity, what is your use case for this? I'm not familiar with 
> this protocol.

I have my personal capsule benjaminja.com running with stargazer and I’m 
building a custom server that will serve multiple capsules at the domain 
level (I talk about the idea a bit more at benjaminja.com/log/2024/06/09-plan-gemini-host/).

To make this work, both servers need to have access to the original tls
layer. I have an nginx stream rule that redirects `benjaminja.com` to
localhost:19651 and `*.geminauts.com` to localhost:19652. For some
reason, I want to see the actual ip addresses of clients, so 
`proxy protocol on;` is set in nginx which injects the real ip
source/dest before the connection's content.

```
PROXY TCP4 123.45.67.89 98.76.54.32 12345 1965\r\n
```

That way the server will know it got a request from 123.45.67.89 instead
of 127.0.0.1 without having to tamper with the connection's body.
Though now that I think about it, having this feature enabled for all
devices would make it easy for any client to spoof their address.
Details
Message ID
<d8c448fb-0868-42c2-a885-d6446362617a@noraa.gay>
In-Reply-To
<447BD553-1D2B-47B4-B30B-428A6BBB2786@benjaminja.info> (view parent)
DKIM signature
pass
Download raw message
> For sure, I'll split up the patch and see what I've messed up with CI.
> Should the typo fix and formatting change both be in the same patch 
> with
> different commits or have a total of 3 patches: typo, formatting, and
> proxy?

Three separate patches as a patch series would be optimal.
Reply to thread Export thread (mbox)