Files
go-meta-listener/mirror/listener.go
2025-05-26 18:35:35 -04:00

282 lines
8.2 KiB
Go

package mirror
import (
"fmt"
"net"
"os"
"strings"
"github.com/go-i2p/go-meta-listener"
"github.com/go-i2p/go-meta-listener/tcp"
"github.com/go-i2p/onramp"
wileedot "github.com/opd-ai/wileedot"
)
type Mirror struct {
*meta.MetaListener
Onions map[string]*onramp.Onion
Garlics map[string]*onramp.Garlic
}
var _ net.Listener = &Mirror{}
func (m *Mirror) Close() error {
log.Println("Closing Mirror")
if err := m.MetaListener.Close(); err != nil {
log.Println("Error closing MetaListener:", err)
} else {
log.Println("MetaListener closed")
}
for _, onion := range m.Onions {
if err := onion.Close(); err != nil {
log.Println("Error closing Onion:", err)
} else {
log.Println("Onion closed")
}
}
for _, garlic := range m.Garlics {
if err := garlic.Close(); err != nil {
log.Println("Error closing Garlic:", err)
} else {
log.Println("Garlic closed")
}
}
log.Println("Mirror closed")
return nil
}
func NewMirror(name string) (*Mirror, error) {
log.Println("Creating new Mirror")
inner := meta.NewMetaListener()
name = strings.TrimSpace(name)
name = strings.ReplaceAll(name, " ", "")
if name == "" {
name = "mirror"
}
log.Printf("Creating new MetaListener with name: '%s'\n", name)
_, port, err := net.SplitHostPort(name)
if err != nil {
port = "3000"
}
onions := make(map[string]*onramp.Onion)
if !DisableTor() {
onion, err := onramp.NewOnion("metalistener-" + name)
if err != nil {
return nil, err
}
log.Println("Created new Onion manager")
onions[port] = onion
}
garlics := make(map[string]*onramp.Garlic)
if !DisableI2P() {
garlic, err := onramp.NewGarlic("metalistener-"+name, "127.0.0.1:7656", onramp.OPT_WIDE)
if err != nil {
return nil, err
}
log.Println("Created new Garlic manager")
garlics[port] = garlic
}
ml := &Mirror{
MetaListener: inner,
Onions: onions,
Garlics: garlics,
}
log.Printf("Mirror created with name: '%s' and port: '%s', '%s'\n", name, port, ml.MetaListener.Addr().String())
return ml, nil
}
func (ml Mirror) Listen(name, addr string) (net.Listener, error) {
log.Println("Starting Mirror Listener")
log.Printf("Actual args: name: '%s' addr: '%s' certDir: '%s' hiddenTls: '%t'\n", name, addr, certDir(), hiddenTls)
// get the port:
_, port, err := net.SplitHostPort(name)
if err != nil {
// check if host is an IP address
if net.ParseIP(name) == nil {
// host = "127.0.0.1"
}
port = "3000"
}
hiddenTls := hiddenTls(port)
localAddr := net.JoinHostPort("127.0.0.1", port)
listener, err := net.Listen("tcp", localAddr)
if err != nil {
return nil, fmt.Errorf("failed to create TCP listener on %s: %w", localAddr, err)
}
tcpListener := listener.(*net.TCPListener)
hardenedListener, err := tcp.Config(*tcpListener)
if err != nil {
log.Fatal(err)
}
log.Printf("TCP listener created on %s\n", localAddr)
if err := ml.AddListener(port, hardenedListener); err != nil {
return nil, err
}
log.Printf("HTTP Local listener added http://%s\n", tcpListener.Addr())
log.Println("Checking for existing onion and garlic listeners")
listenerId := fmt.Sprintf("metalistener-%s-%s", name, port)
log.Println("Listener ID:", listenerId)
// Check if onion and garlic listeners already exist
if ml.Onions[port] == nil && !DisableTor() {
// make a new onion listener
// and add it to the map
log.Println("Creating new onion listener")
onion, err := onramp.NewOnion(listenerId)
if err != nil {
return nil, err
}
log.Println("Onion listener created for port", port)
ml.Onions[port] = onion
}
if ml.Garlics[port] == nil && !DisableI2P() {
// make a new garlic listener
// and add it to the map
log.Println("Creating new garlic listener")
garlic, err := onramp.NewGarlic(listenerId, "127.0.0.1:7656", onramp.OPT_WIDE)
if err != nil {
return nil, err
}
log.Println("Garlic listener created for port", port)
ml.Garlics[port] = garlic
}
if hiddenTls {
// make sure an onion and a garlic listener exist at ml.Onions[port] and ml.Garlics[port]
// and listen on them, check existence first
if !DisableTor() {
onionListener, err := ml.Onions[port].ListenTLS()
if err != nil {
return nil, err
}
oid := fmt.Sprintf("onion-%s", onionListener.Addr().String())
if err := ml.AddListener(oid, onionListener); err != nil {
return nil, err
}
log.Printf("OnionTLS listener added https://%s\n", onionListener.Addr())
}
if !DisableI2P() {
garlicListener, err := ml.Garlics[port].ListenTLS()
if err != nil {
return nil, err
}
gid := fmt.Sprintf("garlic-%s", garlicListener.Addr().String())
if err := ml.AddListener(gid, garlicListener); err != nil {
return nil, err
}
log.Printf("GarlicTLS listener added https://%s\n", garlicListener.Addr())
}
} else {
if !DisableTor() {
onionListener, err := ml.Onions[port].Listen()
if err != nil {
return nil, err
}
oid := fmt.Sprintf("onion-%s", onionListener.Addr().String())
if err := ml.AddListener(oid, onionListener); err != nil {
return nil, err
}
log.Printf("Onion listener added http://%s\n", onionListener.Addr())
}
if !DisableI2P() {
garlicListener, err := ml.Garlics[port].Listen()
if err != nil {
return nil, err
}
gid := fmt.Sprintf("garlic-%s", garlicListener.Addr().String())
if err := ml.AddListener(gid, garlicListener); err != nil {
return nil, err
}
log.Printf("Garlic listener added http://%s\n", garlicListener.Addr())
}
}
if addr != "" {
cfg := wileedot.Config{
Domain: name,
AllowedDomains: []string{name},
CertDir: certDir(),
Email: addr,
}
tlsListener, err := wileedot.New(cfg)
if err != nil {
return nil, err
}
tid := fmt.Sprintf("tls-%s", tlsListener.Addr().String())
if err := ml.AddListener(tid, tlsListener); err != nil {
return nil, err
}
log.Printf("TLS listener added https://%s\n", tlsListener.Addr())
}
return &ml, nil
}
// Listen creates a new Mirror instance and sets up listeners for TLS, Onion, and Garlic.
// It returns the Mirror instance and any error encountered during setup.
// name is the domain name used for the TLS listener, required for Let's Encrypt.
// addr is the email address used for Let's Encrypt registration.
// It is recommended to use a valid email address for production use.
func Listen(name, addr string) (net.Listener, error) {
ml, err := NewMirror(name)
if err != nil {
return nil, err
}
return ml.Listen(name, addr)
}
func DisableTor() bool {
val := os.Getenv("DISABLE_TOR")
if val == "1" || strings.ToLower(val) == "true" {
log.Println("Tor is disabled by environment variable DISABLE_TOR")
return true
}
return false
}
func DisableI2P() bool {
val := os.Getenv("DISABLE_I2P")
if val == "1" || strings.ToLower(val) == "true" {
log.Println("I2P is disabled by environment variable DISABLE_I2P")
return true
}
return false
}
// HIDDEN_TLS is a global variable that determines whether to use hidden TLS.
// It is set to true by default, but can be overridden by the hiddenTls function.
// If the port ends with "22", it will return false, indicating that hidden TLS should not be used.
// This is a useful workaround for SSH connections, which commonly use port 22.
var HIDDEN_TLS = true
func hiddenTls(port string) bool {
// Check if the port is 22, which is commonly used for SSH
if strings.HasSuffix(port, "22") {
log.Println("Port ends with 22, setting hiddenTls to false")
return false
}
// Default to true for other ports
return HIDDEN_TLS
}
var default_CERT_DIR = "./certs"
// CERT_DIR is the directory where certificates are stored.
// It can be overridden by setting the CERT_DIR environment variable.
// if CERT_DIR is not set, it defaults to "./certs".
// if CERT_DIR is set from Go code, it will always return the value set in the code.
// if CERT_DIR is set from the environment, it will return the value from the environment unless overridden by Go code.
var CERT_DIR = default_CERT_DIR
func certDir() string {
// Default certificate directory
certDir := CERT_DIR
if certDir != default_CERT_DIR {
// if the default directory is not used, always return it
return certDir
}
if dir := os.Getenv("CERT_DIR"); dir != "" {
certDir = dir
}
log.Printf("Using certificate directory: %s\n", certDir)
return certDir
}