]> Sergey Matveev's repositories - tofuproxy.git/blobdiff - trip.go
Download link for 0.6.0 release
[tofuproxy.git] / trip.go
diff --git a/trip.go b/trip.go
index 27d4e9e30beacd8239b3827d74fe45a97526d8af..06a6f905c7af6c785aee0359fbe3a9cb375d05e8 100644 (file)
--- a/trip.go
+++ b/trip.go
@@ -1,19 +1,18 @@
-/*
-tofuproxy -- HTTP proxy with TLS certificates management
-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/>.
-*/
+// tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
+//              manager, WARC/geminispace browser
+// Copyright (C) 2021-2024 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
 
@@ -27,8 +26,10 @@ import (
        "time"
 
        "github.com/dustin/go-humanize"
+       "go.stargrave.org/tofuproxy/caches"
        "go.stargrave.org/tofuproxy/fifos"
        "go.stargrave.org/tofuproxy/rounds"
+       ttls "go.stargrave.org/tofuproxy/tls"
 )
 
 var (
@@ -40,7 +41,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{}{
@@ -58,10 +59,12 @@ type Round func(
 ) (bool, error)
 
 func roundTrip(w http.ResponseWriter, req *http.Request) {
+       defer req.Body.Close()
+       fifos.LogReq <- fmt.Sprintf("%s %s", req.Method, req.URL)
        host := strings.TrimSuffix(req.URL.Host, ":443")
        for _, round := range []Round{
-               rounds.RoundNoHead,
-               rounds.RoundLog,
+               rounds.RoundGemini,
+               rounds.RoundWARC,
                rounds.RoundDenySpy,
                rounds.RoundRedditOld,
                rounds.RoundHabrImage,
@@ -71,13 +74,55 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
                }
        }
 
+       reqFlags := []string{}
+       unauthorized := false
+
+       caches.HTTPAuthCacheM.RLock()
+       if creds, ok := caches.HTTPAuthCache[req.URL.Host]; ok {
+               req.SetBasicAuth(creds[0], creds[1])
+               unauthorized = true
+       }
+       caches.HTTPAuthCacheM.RUnlock()
+
+Retry:
        resp, err := transport.RoundTrip(req)
        if err != nil {
-               fifos.SinkErr <- fmt.Sprintf("%s\t%s", req.URL.Host, err.Error())
+               fifos.LogErr <- fmt.Sprintf("%s\t%s", req.URL.Host, err.Error())
                http.Error(w, err.Error(), http.StatusBadGateway)
                return
        }
 
+       if resp.StatusCode == http.StatusUnauthorized {
+               resp.Body.Close()
+               caches.HTTPAuthCacheM.Lock()
+               if unauthorized {
+                       delete(caches.HTTPAuthCache, req.URL.Host)
+               } else {
+                       unauthorized = true
+               }
+               fifos.LogVarious <- fmt.Sprintf(
+                       "%s %s\tHTTP authorization required", req.Method, req.URL.Host,
+               )
+               user, pass, err := authDialog(host, resp.Header.Get("WWW-Authenticate"))
+               if err != nil {
+                       caches.HTTPAuthCacheM.Unlock()
+                       fifos.LogErr <- fmt.Sprintf("%s\t%s", req.URL.Host, err.Error())
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+               caches.HTTPAuthCache[req.URL.Host] = [2]string{user, pass}
+               caches.HTTPAuthCacheM.Unlock()
+               req.SetBasicAuth(user, pass)
+               fifos.LogHTTPAuth <- fmt.Sprintf("%s %s\t%s", req.Method, req.URL, user)
+               goto Retry
+       }
+       if unauthorized {
+               reqFlags = append(reqFlags, "auth")
+       }
+       if resp.TLS != nil && resp.TLS.NegotiatedProtocol != "" {
+               reqFlags = append(reqFlags, resp.TLS.NegotiatedProtocol)
+       }
+
        for k, vs := range resp.Header {
                if _, ok := proxyHeaders[k]; ok {
                        continue
@@ -116,16 +161,16 @@ func roundTrip(w http.ResponseWriter, req *http.Request) {
        }
        resp.Body.Close()
        msg := fmt.Sprintf(
-               "%s %s\t%s\t%s\t%s",
-               req.Method,
-               req.URL.String(),
+               "%s %s\t%s\t%s\t%s\t%s",
+               req.Method, req.URL,
                resp.Status,
                resp.Header.Get("Content-Type"),
                humanize.IBytes(uint64(n)),
+               strings.Join(reqFlags, ","),
        )
        if resp.StatusCode == http.StatusOK {
-               fifos.SinkOK <- msg
+               fifos.LogOK <- msg
        } else {
-               fifos.SinkOther <- msg
+               fifos.LogNonOK <- msg
        }
 }