]> Sergey Matveev's repositories - tofuproxy.git/commitdiff
gemini:// support
authorSergey Matveev <stargrave@stargrave.org>
Thu, 28 Oct 2021 08:43:08 +0000 (11:43 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 28 Oct 2021 08:43:08 +0000 (11:43 +0300)
44 files changed:
README
cmd/certgen/main.go
cmd/enzstd/enzstd.c
cmd/tofuproxy/main.go
cmd/ungzip/main.go
cmd/unzstd/unzstd.c
cmd/warc-extract/main.go
conn.go
doc/gemini.texi [new file with mode: 0644]
doc/index.texi
doc/usage.texi
fifos/del.go
fifos/list.go
fifos/log.go
fifos/spies.go
fifos/start.go
fifos/warcs.go
httpauth.go
rounds/denyFonts.go
rounds/gemini.go [new file with mode: 0644]
rounds/habrImage.go
rounds/noHead.go
rounds/reddit.go
rounds/redirectHTML.go
rounds/spy.go
rounds/transcodeAVIF.go
rounds/transcodeJXL.go
rounds/transcodeWebP.go
rounds/warc.go
tls.go
tls/dane.go [moved from dane.go with 94% similarity]
tls/dial.go [new file with mode: 0644]
tls/tlsauth.go [moved from tlsauth.go with 86% similarity]
tls/verify.go [moved from verify.go with 98% similarity]
trip.go
warc/compressed.go
warc/gzip.go
warc/header.go
warc/open.go
warc/reader.go
warc/record.go
warc/uncompressed.go
warc/uris.go
x509.go

diff --git a/README b/README
index 1f14d6ead426000740ae56694f909f31ed3a9e97..e767a4212ad4693e1258d45107a3e39fc3775639 100644 (file)
--- a/README
+++ b/README
@@ -1,4 +1,5 @@
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+manager, WARC/Gemini browser.
 
 Home page: http://www.tofuproxy.stargrave.org/
 
index ab2b9f686ac5fe7910abc05629bb938496b1617d..93b5e7fe0edbccd578fbf85d977dd131fb22c83b 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index cedb084d80e092072f27b681c924829f504b6d83..ee5e35a2e24c02c66b5dd08bca8bd72c6edecc36 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index d55233979ffceff872b4f19df283e20b0fe476d1..941438d1e6a45749129dbea206440a20f3624d64 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
@@ -27,6 +28,7 @@ import (
        "go.stargrave.org/tofuproxy"
        "go.stargrave.org/tofuproxy/fifos"
        "go.stargrave.org/tofuproxy/rounds"
+       ttls "go.stargrave.org/tofuproxy/tls"
 )
 
 func main() {
@@ -54,9 +56,9 @@ func main() {
 
        fifos.NoTAI = *notai
        fifos.Start(*fifosDir)
-       tofuproxy.Certs = *certs
-       tofuproxy.CCerts = *ccerts
-       tofuproxy.DNSSrv = *dnsSrv
+       ttls.Certs = *certs
+       ttls.CCerts = *ccerts
+       ttls.DNSSrv = *dnsSrv
        tofuproxy.CACert = caCert
        tofuproxy.CAPrv = caPrv
        rounds.WARCOnly = *warcOnly
index 0895aa576fece023266ad4c6fa67a695488aea98..64ec0f7ff31d09e49ab1d2789192ce6373ed1371 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 4864db698b029522b62d6306e5fe50b895d78e13..4815251cf318cac8efd082c31fea86aebd3907a6 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 177d251e3ba2967a94388a2894fb1210caa2c739..85bc2731d930894e387ba7ae59a536a8ad4ecd77 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
diff --git a/conn.go b/conn.go
index 4b88f13740c915c7d4a906462934377dbf5925f1..d2a05a2d6003695a284ca06ca80799875616feca 100644 (file)
--- a/conn.go
+++ b/conn.go
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
diff --git a/doc/gemini.texi b/doc/gemini.texi
new file mode 100644 (file)
index 0000000..73acece
--- /dev/null
@@ -0,0 +1,14 @@
+@node Gemini
+@section Geminispace
+
+You can browse @url{https://en.wikipedia.org/wiki/Gemini_(protocol),
+geminispace} by going to @url{https://gemini/HOST/PATH}, where
+@var{HOST} and @var{PATH} are taken from ordinary gemini URL. For
+example to browse @url{gemini://gemini.circumlunar.space/docs/}, you must go
+through @command{tofuproxy} to @url{https://gemini/gemini.circumlunar.space/docs/}.
+
+If you request @url{https://gemini/} URL with @code{Accept: text/gemini}
+HTTP header, then gemfiles (@file{.gmi} will be passed as-is without
+conversion to HTML.
+
+No @code{INPUT} is supported currently.
index b97500da2287924626be56a2f54b5860f92c1abd..7d552be9cd4dfc5995ac70f995266dd411bf72b9 100644 (file)
@@ -54,6 +54,10 @@ compression ratio for lossless screenshots, is not supported everywhere.
 WARC-related software is written on Python, that nowadays is close to be
 impossible to install and use with all its broken dependencies system.
 
+@item And yet another piece of software is needed for browsing the
+@url{https://en.wikipedia.org/wiki/Gemini_(protocol), geminispace}?
+Too many bicycles already!
+
 @end itemize
 
 That is why I wrote @command{tofuproxy} -- pure Go HTTP proxy, MitMing
@@ -129,11 +133,12 @@ And Go itself tries also to act as a @url{https://http2.github.io/, HTTP/2}
 client too.
 
 @item
-Ability to load @url{https://en.wikipedia.org/wiki/Web_ARChive, WARC}
-files, possibly compressed, possibly continued and replace responses.
+Ability to load and browse @url{https://en.wikipedia.org/wiki/Web_ARChive,
+WARC} archives, possibly @command{gzip}/@command{zstd} compressed.
 
 @item
-
+Ability to browse geminispace, by transparent conversion to HTMLs with
+URL rewriting.
 
 @end itemize
 
index c6f2611b796468165b97b0bc6492319c6ee7f0f0..6ff4cd3328a235ecb6638a91e5923a5907b57ccb 100644 (file)
@@ -65,3 +65,4 @@ $ ( cd fifos ; ./multitail.sh )
 @include spies.texi
 @include certs.texi
 @include warcs.texi
+@include gemini.texi
index 8e5a184ed10c9aad3a13ce7d10b0ea5de8726462..7700baf224e7feedd57291e2608f71c704202f8a 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 8625a814450af01213402b65d732ff1b6d2f6e52..d8f77adc3430721f35f60587539eb9041966c2a0 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
@@ -77,11 +78,15 @@ func listTLSAuth(p string) {
                }
                caches.TLSAuthCacheM.RLock()
                for host, tlsCert := range caches.TLSAuthCache {
-                       cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
-                       if err != nil {
-                               log.Fatalln(err)
+                       subj := "NONE"
+                       if len(tlsCert.Certificate) != 0 {
+                               cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
+                               if err != nil {
+                                       log.Fatalln(err)
+                               }
+                               subj = cert.Subject.String()
                        }
-                       if _, err = fd.WriteString(fmt.Sprintf("%s\t%s\n", host, cert.Subject)); err != nil {
+                       if _, err = fd.WriteString(fmt.Sprintf("%s\t%s\n", host, subj)); err != nil {
                                break
                        }
                }
index 0b717a1602a758d995f5ed5b62fa4d437bc30031..b5308c84a92fe0abded01de05ef48d850729c2d6 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 4edc1517fa234351d622c878dc77f0f1a6fa0e28..2428ba8978a5a8351b1bd6514d4bee0ab9a32f1d 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 2f535e25689b9b55983cf7e648e5a516e72f081d..570f4bc18828033f3ace41f9bc3a6d212f7f6622 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 90e17a0ccceb3848ad947c9aed87ed1bf56435dd..ca8da6dc6c5c67c3b39392e107bed53489bfae6c 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index ade7801242b1a9bd1ab96e06243e98cfd3810d8b..025c42d96bbd12cb5622d852fa683026bfcfa779 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
@@ -27,6 +28,8 @@ import (
        "os/exec"
        "path/filepath"
        "strings"
+
+       ttls "go.stargrave.org/tofuproxy/tls"
 )
 
 func findInNetrc(host string) (string, string) {
@@ -90,7 +93,7 @@ bind . <KeyPress> {switch -exact %%K {
     l login
 }}
 `, realm, userInit, passInit))
-       cmd := exec.Command(CmdWish)
+       cmd := exec.Command(ttls.CmdWish)
        cmd.Stdin = &b
        out, err := cmd.Output()
        if err != nil {
index efc3d4fc40697c549c4210e6f9b2c821eaece698..ea29361cc887c981f4cd301e5c75754b3a266519 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
diff --git a/rounds/gemini.go b/rounds/gemini.go
new file mode 100644 (file)
index 0000000..4876ff7
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
+Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package rounds
+
+import (
+       "bufio"
+       "bytes"
+       "context"
+       "fmt"
+       "html"
+       "io"
+       "log"
+       "net/http"
+       "strconv"
+       "strings"
+
+       "go.stargrave.org/tofuproxy/fifos"
+       ttls "go.stargrave.org/tofuproxy/tls"
+)
+
+const (
+       ContentTypeGemini = "text/gemini"
+       SchemeGemini      = "gemini://"
+       GeminiEntrypoint  = "https://gemini"
+       GeminiPort        = ":1965"
+)
+
+var GemCodeName = map[int]string{
+       10: "INPUT",
+       11: "SENSITIVE INPUT",
+       20: "SUCCESS",
+       30: "REDIRECT - TEMPORARY",
+       31: "REDIRECT - PERMANENT",
+       40: "TEMPORARY FAILURE",
+       41: "SERVER UNAVAILABLE",
+       42: "CGI ERROR",
+       43: "PROXY ERROR",
+       44: "SLOW DOWN",
+       50: "PERMANENT FAILURE",
+       51: "NOT FOUND",
+       52: "GONE",
+       53: "PROXY REQUEST REFUSED",
+       59: "BAD REQUEST",
+       60: "CLIENT CERTIFICATE REQUIRED",
+       61: "CERTIFICATE NOT AUTHORISED",
+       62: "CERTIFICATE NOT VALID",
+}
+
+func absolutizeURL(host, u string, paths ...string) string {
+       host = strings.TrimSuffix(host, GeminiPort)
+       if strings.Contains(u, "://") {
+               return u
+       }
+       if strings.HasPrefix(u, "/") {
+               return GeminiEntrypoint + "/" + host + u
+       }
+       paths = append([]string{GeminiEntrypoint, host}, paths...)
+       paths = append(paths, u)
+       return strings.Join(paths, "/")
+}
+
+func geminifyURL(host, u string, paths ...string) string {
+       u = absolutizeURL(host, u, paths...)
+       if !strings.HasPrefix(u, SchemeGemini) {
+               return u
+       }
+       return GeminiEntrypoint + "/" + strings.TrimPrefix(u, SchemeGemini)
+}
+
+func RoundGemini(
+       host string,
+       resp *http.Response,
+       w http.ResponseWriter,
+       req *http.Request,
+) (bool, error) {
+       if host != "gemini" {
+               return true, nil
+       }
+       paths := strings.Split(strings.TrimPrefix(req.URL.Path, "/"), "/")
+       host, paths = paths[0], paths[1:]
+       hostWithPort := host
+       if !strings.Contains(hostWithPort, ":") {
+               hostWithPort += GeminiPort
+       }
+       conn, err := ttls.DialTLS(context.TODO(), "tcp", hostWithPort)
+       if err != nil {
+               log.Printf("%s: can not dial: %+v\n", req.URL, err)
+               return false, err
+       }
+       _, err = fmt.Fprintf(
+               conn, "%s%s/%s\r\n",
+               SchemeGemini, host, strings.Join(paths, "/"),
+       )
+       if err != nil {
+               log.Printf("%s: can not send request: %+v\n", req.URL, err)
+               return false, err
+       }
+       if len(paths) > 0 && paths[len(paths)-1] == "" {
+               paths = paths[:len(paths)-1]
+       }
+       br := bufio.NewReader(conn)
+       rawResp, err := br.ReadString('\n')
+       if err != nil {
+               log.Printf("%s: can not read response: %+v\n", req.URL, err)
+               return false, err
+       }
+       cols := strings.SplitN(rawResp, " ", 2)
+       if len(cols) < 2 {
+               err = fmt.Errorf("invalid response format: %s", rawResp)
+               log.Printf("%s: %s\n", req.URL, err)
+               return false, err
+       }
+       code, err := strconv.Atoi(cols[0])
+       if err != nil {
+               log.Printf("%s: can not parse response code: %+v\n", req.URL, err)
+               return false, err
+       }
+       codeName := GemCodeName[code]
+       if codeName == "" {
+               codeName = "UNKNOWN"
+       }
+       if 10 <= code && code <= 19 {
+               w.Header().Add("Content-Type", "text/plain")
+               w.WriteHeader(http.StatusBadRequest)
+               fmt.Fprintf(w, "%s\n%d (%s): INPUT is not supported\n", cols[1], code, codeName)
+               return false, nil
+       }
+       if 30 <= code && code <= 39 {
+               w.Header().Add("Content-Type", "text/html")
+               w.WriteHeader(http.StatusOK)
+               u := geminifyURL(host, cols[1], paths...)
+               w.Write([]byte(
+                       fmt.Sprintf(
+                               `<!DOCTYPE html>
+<html><head><title>%d (%s) redirection</title></head>
+<body>Redirection to <a href="%s">%s</a></body></html>`,
+                               code, codeName, u, u,
+                       )))
+               fifos.LogRedir <- fmt.Sprintf(
+                       "%s %s\t%d\t%s", req.Method, req.URL, code, cols[1],
+               )
+               return false, nil
+       }
+       if 40 <= code && code <= 49 {
+               w.Header().Add("Content-Type", "text/plain")
+               w.WriteHeader(http.StatusBadGateway)
+               fmt.Fprintf(w, "%s\n%d (%s)\n", cols[1], code, codeName)
+               return false, nil
+       }
+       if 50 <= code && code <= 59 {
+               w.Header().Add("Content-Type", "text/plain")
+               w.WriteHeader(http.StatusBadGateway)
+               fmt.Fprintf(w, "%s\n%d (%s)\n", cols[1], code, codeName)
+               return false, nil
+       }
+       if 60 <= code && code <= 69 {
+               w.Header().Add("Content-Type", "text/plain")
+               w.WriteHeader(http.StatusUnauthorized)
+               fmt.Fprintf(w, "%s\n%d (%s)\n", cols[1], code, codeName)
+               return false, nil
+       }
+       if !(20 <= code && code <= 29) {
+               err = fmt.Errorf("unknown response code: %d", code)
+               log.Printf("%s: %s\n", req.URL, err)
+               return false, err
+       }
+       contentType := strings.Split(strings.TrimRight(cols[1], "\r\n"), ";")[0]
+       if contentType == ContentTypeGemini &&
+               !strings.Contains(req.Header.Get("Accept"), ContentTypeGemini) {
+               w.Header().Add("Content-Type", "text/html")
+               w.WriteHeader(http.StatusOK)
+               raw, err := io.ReadAll(br)
+               if err != nil {
+                       log.Printf("%s: can not read response body: %+v\n", req.URL, err)
+                       return false, err
+               }
+               var buf bytes.Buffer
+               fmt.Fprintf(&buf, `<!DOCTYPE html>
+<html><head><title>%d (%s)</title></head><body>
+`, code, codeName)
+               pre := false
+               for _, line := range strings.Split(string(raw), "\n") {
+                       if strings.HasPrefix(line, "```") {
+                               if pre {
+                                       buf.WriteString("</pre>\n")
+                               } else {
+                                       buf.WriteString("<pre>" + line[3:] + "\n")
+                               }
+                               pre = !pre
+                               continue
+                       }
+                       if pre {
+                               fmt.Fprintf(&buf, "%s\n", line)
+                               continue
+                       }
+                       if strings.HasPrefix(line, "=> ") {
+                               cols = strings.Fields(line)
+                               u := geminifyURL(host, cols[1], paths...)
+                               switch len(cols) {
+                               case 2:
+                                       fmt.Fprintf(
+                                               &buf, "<a href=\"%s\">%s</a><br/>\n",
+                                               u, html.EscapeString(cols[1]),
+                                       )
+                               default:
+                                       fmt.Fprintf(
+                                               &buf, "<a href=\"%s\">%s</a> (<tt>%s</tt>)<br/>\n",
+                                               u, html.EscapeString(strings.Join(cols[2:], " ")), cols[1],
+                                       )
+                               }
+                               continue
+                       }
+                       if strings.HasPrefix(line, "# ") {
+                               fmt.Fprintf(&buf, "<h1>%s</h1>\n", html.EscapeString(line[2:]))
+                               continue
+                       }
+                       if strings.HasPrefix(line, "## ") {
+                               fmt.Fprintf(&buf, "<h2>%s</h2>\n", html.EscapeString(line[3:]))
+                               continue
+                       }
+                       if strings.HasPrefix(line, "### ") {
+                               fmt.Fprintf(&buf, "<h3>%s</h3>\n", html.EscapeString(line[4:]))
+                               continue
+                       }
+                       if strings.HasPrefix(line, "* ") {
+                               fmt.Fprintf(&buf, "&bullet; %s\n", html.EscapeString(line[2:]))
+                               continue
+                       }
+                       if strings.HasPrefix(line, "> ") {
+                               fmt.Fprintf(
+                                       &buf, "<blockquote><tt>%s</tt></blockquote>\n",
+                                       html.EscapeString(line[2:]),
+                               )
+                               continue
+                       }
+                       fmt.Fprintf(&buf, "%s<br/>\n", html.EscapeString(line))
+               }
+               buf.WriteString("</body></html>\n")
+               _, err = w.Write(buf.Bytes())
+               return false, err
+       }
+       w.Header().Add("Content-Type", contentType)
+       w.WriteHeader(http.StatusOK)
+       _, err = io.Copy(w, br)
+       if err != nil {
+               log.Printf("%s: can not read response body: %+v\n", req.URL, err)
+       }
+       return false, err
+}
index 941832ee76017c243806b7466c866f4d7a5374a4..c9f6fe4c7b0853a99abdc359424f67d8bd6e8b69 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index c61af784a0ed673e6904c71ac324adecbd45d2fc..e1e32fe072620b49d7706252b68c243ea31b68c8 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index fb126ec2ab3580ea57a3271c60d09ca66a816514..8e7ecf3fc4586d93bd8babcd8d3eebd10bd88471 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index d13047a14dc37a139c9d77fb1d6623765866df28..6a7e1fbfd4d2789fd4f920f4b9fe157f7911cb4f 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
@@ -77,7 +78,8 @@ func RoundRedirectHTML(
        location := resp.Header.Get("Location")
        w.Write([]byte(
                fmt.Sprintf(
-                       `<html><head><title>%d %s: %s redirection</title></head>
+                       `<!DOCTYPE html>
+<html><head><title>%d %s: %s redirection</title></head>
 <body>Redirection to <a href="%s">%s</a></body></html>`,
                        resp.StatusCode, http.StatusText(resp.StatusCode),
                        redirType, location, location,
index affc1cbce3842be190bda10e10c842866e310a4d..fcb96f8908cd2683ebc5e94cf67fd9d44516a131 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index c315b195173df542a4812fe11adbdc70e6982941..a5cf4e276c825d900df3d55e7f59326652b0d494 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 94fa07a3ee5c63ec75f8d5c994465570ad89de05..1d3a58e3f8faf78787180f42e7a6be2142210a04 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 2939b08daf076deb2ae65d961c2b149af67f648e..82d3661b85970b9504be2115ba6af2dfbae8c060 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index ddb566c7d40f44648d1caf90b4b25d8fa07b502e..0ca47777a5f939055daf74fb348eca7e2a0567f7 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
diff --git a/tls.go b/tls.go
index dc598622998f6bcdcf893c11991c72b40c9532ed..ffd5084e28695822b346352f2e65461b1b9879a6 100644 (file)
--- a/tls.go
+++ b/tls.go
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
@@ -18,19 +19,14 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 package tofuproxy
 
 import (
-       "context"
        "crypto"
        "crypto/tls"
        "crypto/x509"
        "fmt"
        "log"
-       "net"
        "net/http"
        "strings"
        "time"
-
-       "go.cypherpunks.ru/ucspi"
-       "go.stargrave.org/tofuproxy/fifos"
 )
 
 var (
@@ -101,51 +97,3 @@ func (h *HTTPSHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
        req.URL.Host = h.host
        roundTrip(w, req)
 }
-
-func dialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
-       host := strings.Split(addr, ":")[0]
-       ccg := ClientCertificateGetter{host: host}
-       cfg := tls.Config{
-               VerifyPeerCertificate: func(
-                       rawCerts [][]byte,
-                       verifiedChains [][]*x509.Certificate,
-               ) error {
-                       return verifyCert(host, nil, rawCerts, verifiedChains)
-               },
-               ClientSessionCache:   sessionCache,
-               NextProtos:           []string{"h2", "http/1.1"},
-               GetClientCertificate: ccg.get,
-       }
-       conn, dialErr := tls.Dial(network, addr, &cfg)
-       if dialErr != nil {
-               if _, ok := dialErr.(ErrRejected); ok {
-                       return nil, dialErr
-               }
-               cfg.InsecureSkipVerify = true
-               cfg.VerifyPeerCertificate = func(
-                       rawCerts [][]byte,
-                       verifiedChains [][]*x509.Certificate,
-               ) error {
-                       return verifyCert(host, dialErr, rawCerts, verifiedChains)
-               }
-               var err error
-               conn, err = tls.Dial(network, addr, &cfg)
-               if err != nil {
-                       fifos.LogErr <- fmt.Sprintf("%s\t%s", addr, dialErr.Error())
-                       return nil, err
-               }
-       }
-       connState := conn.ConnectionState()
-       if !connState.DidResume {
-               fifos.LogTLS <- fmt.Sprintf(
-                       "%s\t%s %s %s\t%s\t%s",
-                       addr,
-                       ucspi.TLSVersion(connState.Version),
-                       tls.CipherSuiteName(connState.CipherSuite),
-                       connState.PeerCertificates[0].SignatureAlgorithm,
-                       spkiHash(connState.PeerCertificates[0]),
-                       connState.NegotiatedProtocol,
-               )
-       }
-       return conn, nil
-}
similarity index 94%
rename from dane.go
rename to tls/dane.go
index 9ab950413f8d00e931f01c6108e3123eba2e850d..22fad5dd6e5cccc9e3c34e9b579d88a821138cbc 100644 (file)
--- a/dane.go
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
diff --git a/tls/dial.go b/tls/dial.go
new file mode 100644 (file)
index 0000000..a22dc5f
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
+Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package tofuproxy
+
+import (
+       "context"
+       "crypto/tls"
+       "crypto/x509"
+       "fmt"
+       "net"
+       "strings"
+
+       "go.cypherpunks.ru/ucspi"
+       "go.stargrave.org/tofuproxy/fifos"
+)
+
+var sessionCache = tls.NewLRUClientSessionCache(1024)
+
+func DialTLS(ctx context.Context, network, addr string) (net.Conn, error) {
+       host := strings.Split(addr, ":")[0]
+       ccg := ClientCertificateGetter{host: host}
+       cfg := tls.Config{
+               VerifyPeerCertificate: func(
+                       rawCerts [][]byte,
+                       verifiedChains [][]*x509.Certificate,
+               ) error {
+                       return verifyCert(host, nil, rawCerts, verifiedChains)
+               },
+               ClientSessionCache:   sessionCache,
+               NextProtos:           []string{"h2", "http/1.1"},
+               GetClientCertificate: ccg.get,
+       }
+       conn, dialErr := tls.Dial(network, addr, &cfg)
+       if dialErr != nil {
+               if _, ok := dialErr.(ErrRejected); ok {
+                       return nil, dialErr
+               }
+               cfg.InsecureSkipVerify = true
+               cfg.VerifyPeerCertificate = func(
+                       rawCerts [][]byte,
+                       verifiedChains [][]*x509.Certificate,
+               ) error {
+                       return verifyCert(host, dialErr, rawCerts, verifiedChains)
+               }
+               var err error
+               conn, err = tls.Dial(network, addr, &cfg)
+               if err != nil {
+                       fifos.LogErr <- fmt.Sprintf("%s\t%s", addr, dialErr.Error())
+                       return nil, err
+               }
+       }
+       connState := conn.ConnectionState()
+       if !connState.DidResume {
+               fifos.LogTLS <- fmt.Sprintf(
+                       "%s\t%s %s %s\t%s\t%s",
+                       addr,
+                       ucspi.TLSVersion(connState.Version),
+                       tls.CipherSuiteName(connState.CipherSuite),
+                       connState.PeerCertificates[0].SignatureAlgorithm,
+                       spkiHash(connState.PeerCertificates[0]),
+                       connState.NegotiatedProtocol,
+               )
+       }
+       return conn, nil
+}
similarity index 86%
rename from tlsauth.go
rename to tls/tlsauth.go
index fd9839a54b39885e2abaf6916aa524b9393687a7..1d47670d9d2d2d70e47c2464c997c168a4851f01 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
@@ -75,6 +76,7 @@ grid .login
 
 bind . <KeyPress> {switch -exact %%K {
     q {exit 0} ; # reject once
+    n {puts "0:NONE" ; exit}
     l login
 }}
 
@@ -100,6 +102,9 @@ foreach sigScheme {%s} {
        }
        certs := make([]*x509.Certificate, 0, len(ents))
        tlsCerts := make([]*tls.Certificate, 0, len(ents))
+       b.WriteString(".lb insert end \"0: NONE\"\n")
+       certs = append(certs, nil)
+       tlsCerts = append(tlsCerts, nil)
        for i, ent := range ents {
                p := filepath.Join(CCerts, ent.Name())
                _, cert, err := ucspi.CertificateFromFile(p)
@@ -115,7 +120,7 @@ foreach sigScheme {%s} {
                        Certificate: [][]byte{cert.Raw},
                        PrivateKey:  prv,
                })
-               b.WriteString(fmt.Sprintf(".lb insert end \"%d: %s\"\n", i, cert.Subject))
+               b.WriteString(fmt.Sprintf(".lb insert end \"%d: %s\"\n", i+1, cert.Subject))
        }
        // ioutil.WriteFile("/tmp/tls-auth-dialog.tcl", b.Bytes(), 0666)
        cmd := exec.Command(CmdWish)
@@ -133,6 +138,13 @@ foreach sigScheme {%s} {
        if err != nil {
                return &tls.Certificate{}, nil
        }
+       if i == 0 {
+               dummy := tls.Certificate{}
+               caches.TLSAuthCacheM.Lock()
+               caches.TLSAuthCache[g.host] = &dummy
+               caches.TLSAuthCacheM.Unlock()
+               return &dummy, nil
+       }
        fifos.LogTLSAuth <- fmt.Sprintf("%s\t%s", g.host, certs[i].Subject)
        caches.TLSAuthCacheM.Lock()
        caches.TLSAuthCache[g.host] = tlsCerts[i]
similarity index 98%
rename from verify.go
rename to tls/verify.go
index dc852a8b747a7b31ed67f7b5c28a1702bc851616..467dbb3fe3dfef3c32cf89c4770a2a296c8d6848 100644 (file)
--- a/verify.go
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
diff --git a/trip.go b/trip.go
index b217750205cf26561be27e65f47ce20bb0ef3e2c..60262295ed5f2c3d91db632d30b9f7c4d9b6bced 100644 (file)
--- a/trip.go
+++ b/trip.go
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
@@ -30,6 +31,7 @@ import (
        "go.stargrave.org/tofuproxy/caches"
        "go.stargrave.org/tofuproxy/fifos"
        "go.stargrave.org/tofuproxy/rounds"
+       ttls "go.stargrave.org/tofuproxy/tls"
 )
 
 var (
@@ -41,7 +43,7 @@ var (
                MaxIdleConns:        http.DefaultTransport.(*http.Transport).MaxIdleConns,
                IdleConnTimeout:     http.DefaultTransport.(*http.Transport).IdleConnTimeout * 2,
                TLSHandshakeTimeout: time.Minute,
-               DialTLSContext:      dialTLS,
+               DialTLSContext:      ttls.DialTLS,
                ForceAttemptHTTP2:   true,
        }
        proxyHeaders = map[string]struct{}{
@@ -64,6 +66,7 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
        host := strings.TrimSuffix(req.URL.Host, ":443")
        for _, round := range []Round{
                rounds.RoundNoHead,
+               rounds.RoundGemini,
                rounds.RoundWARC,
                rounds.RoundDenySpy,
                rounds.RoundRedditOld,
index b1de1ce31fa8ce0f7d45eb76f2c7292d031ac189..06fc53e3b79e0b88f01d5ed76aa6c81a0a9a0940 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 335a26eea63158e43f8686ea2ecf9cb818ab15f4..f68da5c46b53d51885fa5166753d7c5d82db719d 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index cb1b7a9a13f714c3cf41b903dc05f45bb8a4d4cd..53a6d414a77fd1b80a1d7f3662d1044a171a7840 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 22792d5b09513aed61972daf4443c60824e7fece..f802bbdbba23056b872975da3384bd54385acd3b 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 06a123b4eece7fc352657a9d95491f6983518571..e73413622902c12b25f67c060d3a9339a6fa9137 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index a2fa6bc5a2370838cdb4c4d5d867003a2a037ee9..3a9843571a05e53bb86e8756a09a8674aa7c0276 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 5dd60c5608c2b2f496daa753b1b4da0897e04568..5164e3dc5e51ffe7d387202602bee259eca1cf39 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
index 4d8c1a34b20fef8aa77d5ba64f89d9835535b9dc..12bf70d6abb6b259861451ae1684bfcac038770a 100644 (file)
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify
diff --git a/x509.go b/x509.go
index 2d27b1af3947718c86581cd66a9f9b84165f1836..f4e4e7fb77a2df938d1fee7dc54e887ace881ba3 100644 (file)
--- a/x509.go
+++ b/x509.go
@@ -1,5 +1,6 @@
 /*
-tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
+             manager, WARC/Gemini browser
 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
 
 This program is free software: you can redistribute it and/or modify