privtracker/main.go
2022-03-23 00:16:58 +01:00

215 lines
5.3 KiB
Go

package main
import (
"crypto/tls"
"log"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/monitor"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/jackpal/bencode-go"
"golang.org/x/crypto/acme/autocert"
)
func main() {
go Cleanup()
config := fiber.Config{
ServerHeader: "privtracker",
ReadTimeout: time.Second * 245,
}
domains, tls := os.LookupEnv("DOMAINS")
if !tls {
config.EnableTrustedProxyCheck = true
config.TrustedProxies = []string{"127.0.0.1"}
config.ProxyHeader = fiber.HeaderXForwardedFor
}
app := fiber.New(config)
app.Use(recover.New())
app.Use(myLogger())
app.Use(hsts)
app.Get("/", docs)
app.Static("/", "docs", fiber.Static{
MaxAge: 3600 * 24 * 7,
Compress: true,
CacheDuration: time.Hour,
})
app.Get("/dashboard", monitor.New())
app.Get("/:room/announce", announce)
app.Get("/:room/scrape", scrape)
app.Server().LogAllErrors = true
if tls {
go redirect80(config)
split := strings.Split(domains, ",")
log.Fatal(app.Listener(myListener(split...)))
} else {
port := os.Getenv("PORT")
if port == "" {
port = "1337"
}
log.Fatal(app.Listen(":" + port))
}
}
func myListener(domains ...string) net.Listener {
homeDir, err := os.UserHomeDir()
if err != nil {
panic(err)
}
cacheDir := filepath.Join(homeDir, ".cache", "golang-autocert")
m := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(domains...),
Cache: autocert.DirCache(cacheDir),
}
cfg := &tls.Config{
GetCertificate: m.GetCertificate,
NextProtos: []string{
"http/1.1", "acme-tls/1",
},
// MinVersion: tls.VersionTLS12,
// CipherSuites: []uint16{
// tls.TLS_AES_128_GCM_SHA256,
// tls.TLS_AES_256_GCM_SHA384,
// tls.TLS_CHACHA20_POLY1305_SHA256,
// tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
// tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
// tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
// tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
// tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
// },
}
ln, err := tls.Listen("tcp", ":443", cfg)
if err != nil {
panic(err)
}
return ln
}
func redirect80(config fiber.Config) {
config.DisableStartupMessage = true
app := fiber.New(config)
app.Use(func(c *fiber.Ctx) error {
return c.Redirect("https://privtracker.com/", fiber.StatusMovedPermanently)
})
log.Print(app.Listen(":80"))
}
func myLogger() fiber.Handler {
loggerConfig := logger.ConfigDefault
loggerConfig.Format = "${status} - ${latency} ${ip} ${method} ${path} ${bytesSent} - ${referer} - ${ua}\n"
return logger.New(loggerConfig)
}
func hsts(c *fiber.Ctx) error {
c.Set("Strict-Transport-Security", "max-age=31536000")
return c.Next()
}
func docs(c *fiber.Ctx) error {
if c.Hostname() != "privtracker.com" {
return c.Redirect("https://privtracker.com/", fiber.StatusMovedPermanently)
}
return c.Next()
}
type AnnounceRequest struct {
InfoHash string `query:"info_hash"`
PeerID string `query:"peer_id"`
IP string `query:"ip"`
Port uint16 `query:"port"`
Uploaded uint `query:"uploaded"`
Downloaded uint `query:"downloaded"`
Left uint `query:"left"`
Numwant uint `query:"numwant"`
Key string `query:"key"`
Compact bool `query:"compact"`
SupportCrypto bool `query:"supportcrypto"`
Event string `query:"event"`
}
func (req *AnnounceRequest) IsSeeding() bool {
return req.Left == 0
}
type AnnounceResponse struct {
Interval int `bencode:"interval"`
Complete int `bencode:"complete"`
Incomplete int `bencode:"incomplete"`
Peers []byte `bencode:"peers"`
PeersIPv6 []byte `bencode:"peers_ipv6"`
}
func announce(c *fiber.Ctx) error {
var req AnnounceRequest
err := c.QueryParser(&req)
if err != nil {
return err
}
req.IP = c.IP()
if req.Numwant == 0 {
req.Numwant = 30
}
switch req.Event {
case "stopped":
DeletePeer(c.Params("room"), req.InfoHash, req.IP, req.Port)
case "completed":
GraduateLeecher(c.Params("room"), req.InfoHash, req.IP, req.Port)
default:
PutPeer(c.Params("room"), req.InfoHash, req.IP, req.Port, req.IsSeeding())
}
peersIPv4, peersIPv6, numSeeders, numLeechers := GetPeers(c.Params("room"), req.InfoHash, req.IP, req.Port, req.IsSeeding(), req.Numwant)
interval := 120
if numSeeders == 0 {
interval /= 2
} else if numLeechers == 0 {
interval *= 2
}
resp := AnnounceResponse{
Interval: interval,
Complete: numSeeders,
Incomplete: numLeechers,
Peers: peersIPv4,
PeersIPv6: peersIPv6,
}
defer c.Response().SetConnectionClose()
return bencode.Marshal(c, resp)
}
type ScrapeRequest struct {
InfoHash string `query:"info_hash"`
}
type ScrapeResponse struct {
Files map[string]Stat `bencode:"files"`
}
type Stat struct {
Complete int `bencode:"complete"`
Incomplete int `bencode:"incomplete"`
// Downloaded uint `bencode:"downloaded"`
}
func scrape(c *fiber.Ctx) error {
var req ScrapeRequest
err := c.QueryParser(&req)
if err != nil {
return err
}
numSeeders, numLeechers := GetStats(c.Params("room"), req.InfoHash)
resp := ScrapeResponse{
Files: map[string]Stat{
req.InfoHash: {
Complete: numSeeders,
Incomplete: numLeechers,
},
},
}
return bencode.Marshal(c, resp)
}