diff options
| author | Gab <24553253+gabrix73@users.noreply.github.com> | 2025-10-24 18:07:59 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-24 18:07:59 +0200 |
| commit | b4aa0d88f2e01a27a937e33f4c3594a72dee2500 (patch) | |
| tree | 58b2bd5891f36dc054c9397fdfa4957431729074 | |
| parent | f668c146260f701b8bf9ff5d5937814ac1e1d664 (diff) | |
| download | fog-b4aa0d88f2e01a27a937e33f4c3594a72dee2500.tar.gz fog-b4aa0d88f2e01a27a937e33f4c3594a72dee2500.tar.xz fog-b4aa0d88f2e01a27a937e33f4c3594a72dee2500.zip | |
Delete pluto2.go
| -rw-r--r-- | pluto2.go | 851 |
1 files changed, 0 insertions, 851 deletions
diff --git a/pluto2.go b/pluto2.go deleted file mode 100644 index 137c8e0..0000000 --- a/pluto2.go +++ /dev/null @@ -1,851 +0,0 @@ -package main - -import ( - "bytes" - "crypto/rand" - "crypto/sha512" - "encoding/base64" - "encoding/binary" - "errors" - "flag" - "fmt" - "io" - "log" - "math" - "math/big" - "net" - "net/smtp" - "net/textproto" - "regexp" - "strings" - "sync" - "time" - - "golang.org/x/net/proxy" -) - -const ( - TorSocksProxyAddr = "127.0.0.1:9050" - RelayWorkerCount = 5 - DeliveryTimeout = 30 * time.Second - MixnetBatchWindow = 30 * time.Second - CoverTrafficInterval = 15 * time.Second - MessageIDCacheDuration = 24 * time.Hour - PaddingSizeUnit = 8 * 1024 - MinDelay = 100 * time.Millisecond - MaxDelay = 2 * time.Second - RateLimitPerIP = 10 - RateLimitWindow = 1 * time.Minute - MXLookupTimeout = 5 * time.Second -) - -var ( - emailRegExp *regexp.Regexp - localPartRegex *regexp.Regexp - domainRegex *regexp.Regexp -) - -func init() { - emailRegExp = regexp.MustCompile(`^[a-zA-Z0-9._%+=\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) - localPartRegex = regexp.MustCompile(`^[a-zA-Z0-9._+=\-]+$`) - domainRegex = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$`) -} - -func validateLocalPart(localPart string) error { - if len(localPart) == 0 || len(localPart) > 64 { - return errors.New("local part must be 1-64 characters") - } - if strings.HasPrefix(localPart, ".") || strings.HasSuffix(localPart, ".") { - return errors.New("local part cannot start or end with a dot") - } - if strings.Contains(localPart, "..") { - return errors.New("local part cannot contain consecutive dots") - } - if !localPartRegex.MatchString(localPart) { - return errors.New("local part contains invalid characters") - } - return nil -} - -func validateDomain(domain string) error { - domain = strings.ToLower(strings.TrimSpace(domain)) - if strings.HasSuffix(domain, ".") { - domain = domain[:len(domain)-1] - } - if len(domain) == 0 || len(domain) > 253 { - return errors.New("domain must be 1-253 characters") - } - if isOnionDomain(domain) { - onionName := strings.TrimSuffix(domain, ".onion") - if len(onionName) != 16 && len(onionName) != 56 { - return errors.New("invalid .onion domain length") - } - validOnion := regexp.MustCompile(`^[a-z2-7]+$`) - if !validOnion.MatchString(onionName) { - return errors.New("invalid .onion domain format") - } - return nil - } - if !domainRegex.MatchString(domain) { - return errors.New("invalid domain format") - } - labels := strings.Split(domain, ".") - if len(labels) < 2 { - return errors.New("domain must have at least two labels") - } - for _, label := range labels { - if len(label) == 0 || len(label) > 63 { - return errors.New("domain label must be 1-63 characters") - } - } - return nil -} - -func ValidateEmailAddress(address string) (string, string, error) { - address = strings.TrimSpace(address) - address = strings.Trim(address, "<>") - if !strings.Contains(address, "@") { - return "", "", errors.New("invalid email address: missing '@'") - } - sepInd := strings.LastIndex(address, "@") - if sepInd == 0 || sepInd == len(address)-1 { - return "", "", errors.New("invalid email address: empty local part or domain") - } - localPart := address[:sepInd] - domain := address[sepInd+1:] - if err := validateLocalPart(localPart); err != nil { - return "", "", fmt.Errorf("invalid local part: %w", err) - } - if err := validateDomain(domain); err != nil { - return "", "", fmt.Errorf("invalid domain: %w", err) - } - return localPart, domain, nil -} - -func validateServerName(name string) error { - name = strings.TrimSpace(name) - if name == "" { - return errors.New("server name cannot be empty") - } - if !isOnionDomain(name) { - return errors.New("server name must be a .onion domain") - } - onionName := strings.TrimSuffix(strings.ToLower(name), ".onion") - if len(onionName) != 56 { - return errors.New("server name must be a v3 .onion address (56 characters)") - } - validOnionV3 := regexp.MustCompile(`^[a-z2-7]{56}$`) - if !validOnionV3.MatchString(onionName) { - return errors.New("invalid v3 .onion address format") - } - return nil -} - -func hasMXRecords(domain string) bool { - domain = strings.ToLower(strings.TrimSpace(domain)) - if strings.HasSuffix(domain, ".") { - domain = domain[:len(domain)-1] - } - done := make(chan bool, 1) - var hasMX bool - go func() { - mxRecords, err := net.LookupMX(domain) - hasMX = err == nil && len(mxRecords) > 0 - done <- true - }() - select { - case <-done: - return hasMX - case <-time.After(MXLookupTimeout): - return false - } -} - -type MessageIDCache struct { - cache map[string]time.Time - mu sync.RWMutex -} - -func NewMessageIDCache() *MessageIDCache { - cache := &MessageIDCache{cache: make(map[string]time.Time)} - go cache.cleanupLoop() - return cache -} - -func (c *MessageIDCache) Has(messageID string) bool { - c.mu.RLock() - defer c.mu.RUnlock() - expiry, exists := c.cache[messageID] - if !exists { - return false - } - return time.Now().Before(expiry) -} - -func (c *MessageIDCache) Add(messageID string) bool { - c.mu.Lock() - defer c.mu.Unlock() - if _, exists := c.cache[messageID]; exists { - return false - } - c.cache[messageID] = time.Now().Add(MessageIDCacheDuration) - return true -} - -func (c *MessageIDCache) cleanupLoop() { - ticker := time.NewTicker(1 * time.Hour) - defer ticker.Stop() - for range ticker.C { - c.mu.Lock() - now := time.Now() - for id, expiry := range c.cache { - if now.After(expiry) { - delete(c.cache, id) - } - } - c.mu.Unlock() - } -} - -type RateLimiter struct { - requests map[string][]time.Time - mu sync.RWMutex -} - -func NewRateLimiter() *RateLimiter { - limiter := &RateLimiter{requests: make(map[string][]time.Time)} - go limiter.cleanupLoop() - return limiter -} - -func (rl *RateLimiter) Allow(clientIP string) bool { - rl.mu.Lock() - defer rl.mu.Unlock() - now := time.Now() - cutoff := now.Add(-RateLimitWindow) - requests := rl.requests[clientIP] - var validRequests []time.Time - for _, t := range requests { - if t.After(cutoff) { - validRequests = append(validRequests, t) - } - } - if len(validRequests) >= RateLimitPerIP { - rl.requests[clientIP] = validRequests - return false - } - validRequests = append(validRequests, now) - rl.requests[clientIP] = validRequests - return true -} - -func (rl *RateLimiter) cleanupLoop() { - ticker := time.NewTicker(5 * time.Minute) - defer ticker.Stop() - for range ticker.C { - rl.mu.Lock() - now := time.Now() - cutoff := now.Add(-RateLimitWindow) - for ip, requests := range rl.requests { - var validRequests []time.Time - for _, t := range requests { - if t.After(cutoff) { - validRequests = append(validRequests, t) - } - } - if len(validRequests) == 0 { - delete(rl.requests, ip) - } else { - rl.requests[ip] = validRequests - } - } - rl.mu.Unlock() - } -} - -func ApplyAdaptivePadding(data []byte) []byte { - originalLen := len(data) - targetSize := ((originalLen / PaddingSizeUnit) + 1) * PaddingSizeUnit - if originalLen >= targetSize { - return data - } - padded := make([]byte, targetSize) - copy(padded, data) - paddingLen := targetSize - originalLen - padding := make([]byte, paddingLen) - rand.Read(padding) - copy(padded[originalLen:], padding) - return padded -} - -func RandomDelay() { - delayNs := MinDelay.Nanoseconds() + int64(cryptoRandInt63n(MaxDelay.Nanoseconds()-MinDelay.Nanoseconds())) - time.Sleep(time.Duration(delayNs)) -} - -func cryptoRandInt63n(n int64) int64 { - if n <= 0 { - return 0 - } - max := big.NewInt(n) - result, _ := rand.Int(rand.Reader, max) - return result.Int64() -} - -type MixnetBatcher struct { - batch []*Envelope - batchMu sync.Mutex - outputQueue chan *Envelope - stopChan chan struct{} -} - -func NewMixnetBatcher(outputQueue chan *Envelope) *MixnetBatcher { - batcher := &MixnetBatcher{ - batch: make([]*Envelope, 0), - outputQueue: outputQueue, - stopChan: make(chan struct{}), - } - go batcher.batchLoop() - return batcher -} - -func (mb *MixnetBatcher) Add(envelope *Envelope) { - mb.batchMu.Lock() - mb.batch = append(mb.batch, envelope) - mb.batchMu.Unlock() -} - -func (mb *MixnetBatcher) batchLoop() { - ticker := time.NewTicker(MixnetBatchWindow) - defer ticker.Stop() - for { - select { - case <-ticker.C: - mb.processBatch() - case <-mb.stopChan: - return - } - } -} - -func (mb *MixnetBatcher) processBatch() { - mb.batchMu.Lock() - if len(mb.batch) == 0 { - mb.batchMu.Unlock() - return - } - batch := make([]*Envelope, len(mb.batch)) - copy(batch, mb.batch) - mb.batch = make([]*Envelope, 0) - mb.batchMu.Unlock() - shuffleBatch(batch) - for _, env := range batch { - RandomDelay() - select { - case mb.outputQueue <- env: - default: - } - } -} - -func shuffleBatch(batch []*Envelope) { - for i := len(batch) - 1; i > 0; i-- { - j := int(cryptoRandInt63n(int64(i + 1))) - batch[i], batch[j] = batch[j], batch[i] - } -} - -func (mb *MixnetBatcher) Stop() { - close(mb.stopChan) -} - -type CoverTrafficGenerator struct { - outputQueue chan *Envelope - stopChan chan struct{} -} - -func NewCoverTrafficGenerator(outputQueue chan *Envelope) *CoverTrafficGenerator { - gen := &CoverTrafficGenerator{ - outputQueue: outputQueue, - stopChan: make(chan struct{}), - } - go gen.generateLoop() - return gen -} - -func (ctg *CoverTrafficGenerator) generateLoop() { - ticker := time.NewTicker(CoverTrafficInterval) - defer ticker.Stop() - firstNames := []string{"john", "mary", "david", "sarah", "mike", "emma", "james", "lisa", "robert", "anna"} - lastNames := []string{"smith", "johnson", "williams", "brown", "jones", "garcia", "miller", "davis", "rodriguez", "martinez"} - for { - select { - case <-ticker.C: - firstName := firstNames[cryptoRandInt63n(int64(len(firstNames)))] - lastName := lastNames[cryptoRandInt63n(int64(len(lastNames)))] - randomNum := cryptoRandInt63n(9999) - var dummyAddr string - switch cryptoRandInt63n(3) { - case 0: - dummyAddr = fmt.Sprintf("%s.%s%d@gmail.com", firstName, lastName, randomNum) - case 1: - dummyAddr = fmt.Sprintf("%s%s%d@gmail.com", firstName, lastName, randomNum) - default: - dummyAddr = fmt.Sprintf("%s.%s@gmail.com", firstName, lastName) - } - dummyData := make([]byte, 1024+int(cryptoRandInt63n(2048))) - rand.Read(dummyData) - envelope := &Envelope{ - MessageFrom: fmt.Sprintf("noreply%d@notifications.com", cryptoRandInt63n(999)), - MessageTo: dummyAddr, - MessageData: bytes.NewReader(dummyData), - ReceivedAt: time.Now(), - RetryCount: 0, - IsCoverTraffic: true, - } - select { - case ctg.outputQueue <- envelope: - default: - } - case <-ctg.stopChan: - return - } - } -} - -func (ctg *CoverTrafficGenerator) Stop() { - close(ctg.stopChan) -} - -type Server struct { - Name string - Addr string - Handler Handler - MessageIDCache *MessageIDCache - RateLimiter *RateLimiter - MixnetBatcher *MixnetBatcher - CoverTraffic *CoverTrafficGenerator -} - -type conn struct { - remoteAddr string - server *Server - rwc net.Conn - text *textproto.Conn - fromAgent string - mailFrom string - mailTo []string - mailData *bytes.Buffer - helloReceived bool - quitSent bool - mu sync.Mutex -} - -type Envelope struct { - MessageFrom string - MessageTo string - MessageData io.Reader - ReceivedAt time.Time - RetryCount int - MessageID string - IsCoverTraffic bool -} - -type HandlerFunc func(envelope *Envelope) error - -func (f HandlerFunc) ServeSMTP(envelope *Envelope) error { - return f(envelope) -} - -type Handler interface { - ServeSMTP(envelope *Envelope) error -} - -var ( - mailQueue chan *Envelope - mailQueueMutex sync.Mutex -) - -func (srv *Server) newConn(rwc net.Conn) (*conn, error) { - c := &conn{ - remoteAddr: rwc.RemoteAddr().String(), - server: srv, - rwc: rwc, - text: textproto.NewConn(rwc), - mailTo: make([]string, 0), - } - return c, nil -} - -func (srv *Server) ListenAndServe() error { - if srv.Name == "" { - srv.Name = "localhost" - } - addr := srv.Addr - if addr == "" { - addr = ":2525" - } - ln, err := net.Listen("tcp", addr) - if err != nil { - return err - } - return srv.Serve(ln) -} - -func (srv *Server) Serve(l net.Listener) error { - defer l.Close() - var tempDelay time.Duration - for { - rw, e := l.Accept() - if e != nil { - if ne, ok := e.(net.Error); ok && ne.Temporary() { - if tempDelay == 0 { - tempDelay = 5 * time.Millisecond - } else { - tempDelay *= 2 - } - if max := 1 * time.Second; tempDelay > max { - tempDelay = max - } - time.Sleep(tempDelay) - continue - } - return e - } - tempDelay = 0 - c, err := srv.newConn(rw) - if err != nil { - continue - } - go c.serve() - } -} - -func (c *conn) serve() { - clientIP := extractIP(c.remoteAddr) - if !c.server.RateLimiter.Allow(clientIP) { - RandomDelay() - c.text.Close() - c.rwc.Close() - return - } - RandomDelay() - err := c.text.PrintfLine("%d %s ESMTP", 220, c.server.Name) - if err != nil { - return - } - for !c.quitSent && err == nil { - err = c.readCommand() - } - c.text.Close() - c.rwc.Close() -} - -func extractIP(addr string) string { - host, _, err := net.SplitHostPort(addr) - if err != nil { - return addr - } - return host -} - -func (c *conn) resetSession() { - c.mailFrom = "" - c.mailTo = make([]string, 0) - c.mailData = nil -} - -func isOnionDomain(domain string) bool { - domain = strings.ToLower(strings.TrimSpace(domain)) - if domain == "" { - return false - } - if strings.HasSuffix(domain, ".") { - domain = domain[:len(domain)-1] - } - return strings.HasSuffix(domain, ".onion") -} - -func SplitAddress(address string) (string, string, error) { - localPart, domain, err := ValidateEmailAddress(address) - if err != nil { - return "", "", err - } - if !isOnionDomain(domain) { - return "", "", errors.New("only .onion domains are allowed for relay") - } - return localPart, domain, nil -} - -func extractDomainFromAddress(address string) string { - sepInd := strings.LastIndex(address, "@") - if sepInd == -1 { - return "" - } - return address[sepInd+1:] -} - -func (c *conn) readCommand() error { - s, err := c.text.ReadLine() - if err != nil { - return err - } - RandomDelay() - parts := strings.Split(s, " ") - if len(parts) <= 0 { - return c.text.PrintfLine("%d %s", 500, "Command not recognized") - } - parts[0] = strings.ToUpper(parts[0]) - switch parts[0] { - case "HELO", "EHLO": - if len(parts) < 2 { - return c.text.PrintfLine("%d %s", 501, "Not enough arguments") - } - c.fromAgent = parts[1] - c.resetSession() - c.helloReceived = true - responses := []string{ - fmt.Sprintf("%d-%s", 250, c.server.Name), - fmt.Sprintf("%d-%s", 250, "PIPELINING"), - fmt.Sprintf("%d %s", 250, "SMTPUTF8"), - } - for i, resp := range responses { - if i == len(responses)-1 { - resp = strings.Replace(resp, "-", " ", 1) - } - if err := c.text.PrintfLine(resp); err != nil { - return err - } - } - return nil - case "MAIL": - if c.mailFrom != "" { - return c.text.PrintfLine("%d %s", 503, "MAIL command already received") - } - if len(parts) < 2 { - return c.text.PrintfLine("%d %s", 501, "Not enough arguments") - } - if !strings.HasPrefix(strings.ToUpper(parts[1]), "FROM:") { - return c.text.PrintfLine("%d %s", 501, "MAIL command must be immediately succeeded by 'FROM:'") - } - from := strings.TrimPrefix(parts[1], "FROM:") - from = strings.TrimPrefix(from, "from:") - from = strings.Trim(from, "<>") - if !emailRegExp.MatchString(from) { - return c.text.PrintfLine("%d %s", 501, "MAIL command contained invalid address") - } - _, _, err := ValidateEmailAddress(from) - if err != nil { - return c.text.PrintfLine("%d %s", 501, "Invalid email address format") - } - c.mailFrom = from - return c.text.PrintfLine("%d %s", 250, "Ok") - case "RCPT": - if c.mailFrom == "" { - return c.text.PrintfLine("%d %s", 503, "Bad sequence of commands") - } - if len(parts) < 2 { - return c.text.PrintfLine("%d %s", 501, "Not enough arguments") - } - if !strings.HasPrefix(strings.ToUpper(parts[1]), "TO:") { - return c.text.PrintfLine("%d %s", 501, "RCPT command must be immediately succeeded by 'TO:'") - } - to := strings.TrimPrefix(parts[1], "TO:") - to = strings.TrimPrefix(to, "to:") - to = strings.Trim(to, "<>") - if !emailRegExp.MatchString(to) { - return c.text.PrintfLine("%d %s", 501, "RCPT command contained invalid address") - } - _, domain, err := ValidateEmailAddress(to) - if err != nil { - return c.text.PrintfLine("%d %s", 501, "Invalid email address format") - } - if !isOnionDomain(domain) { - if !hasMXRecords(domain) { - return c.text.PrintfLine("%d %s", 550, "Mailbox unavailable") - } - } - c.mailTo = append(c.mailTo, to) - return c.text.PrintfLine("%d %s", 250, "Ok") - case "DATA": - if len(c.mailTo) == 0 || c.mailFrom == "" { - return c.text.PrintfLine("%d %s", 503, "Bad sequence of commands") - } - if err := c.text.PrintfLine("%d %s", 354, "End data with <CR><LF>.<CR><LF>"); err != nil { - return err - } - data, err := c.text.ReadDotBytes() - if err != nil { - return err - } - c.mailData = bytes.NewBuffer(data) - messageID := generateMessageID() - if c.server.MessageIDCache.Has(messageID) { - return c.text.PrintfLine("%d %s", 554, "Duplicate message detected") - } - c.server.MessageIDCache.Add(messageID) - for _, recipient := range c.mailTo { - env := &Envelope{ - MessageFrom: c.mailFrom, - MessageTo: recipient, - MessageData: bytes.NewReader(c.mailData.Bytes()), - ReceivedAt: time.Now(), - RetryCount: 0, - MessageID: messageID, - IsCoverTraffic: false, - } - if err := c.server.Handler.ServeSMTP(env); err != nil { - return c.text.PrintfLine("%d %s", 554, "Transaction failed") - } - } - c.resetSession() - return c.text.PrintfLine("%d %s", 250, "OK") - case "RSET": - c.resetSession() - return c.text.PrintfLine("%d %s", 250, "Ok") - case "VRFY", "EXPN", "HELP", "NOOP": - return c.text.PrintfLine("%d %s", 250, "OK") - case "QUIT": - c.quitSent = true - return c.text.PrintfLine("%d %s", 221, "Bye") - default: - return c.text.PrintfLine("%d %s", 500, "Command not recognized") - } -} - -func generateMessageID() string { - randomBytes := make([]byte, 32) - rand.Read(randomBytes) - timestamp := make([]byte, 8) - binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UnixNano())) - combined := append(randomBytes, timestamp...) - h := sha512.Sum512(combined) - return base64.URLEncoding.EncodeToString(h[:32]) -} - -type SimpleHandler struct { - batcher *MixnetBatcher -} - -func (h *SimpleHandler) ServeSMTP(envelope *Envelope) error { - h.batcher.Add(envelope) - return nil -} - -func smtpRelay(envelope *Envelope) error { - if envelope.IsCoverTraffic { - return nil - } - _, domain, err := SplitAddress(envelope.MessageTo) - if err != nil { - return fmt.Errorf("invalid recipient address: %w", err) - } - targetAddr := net.JoinHostPort(domain, "25") - jitter := time.Duration(cryptoRandInt63n(int64(5 * time.Second))) - time.Sleep(jitter) - dialer := &net.Dialer{Timeout: DeliveryTimeout} - torDialer, err := proxy.SOCKS5("tcp", TorSocksProxyAddr, nil, dialer) - if err != nil { - return fmt.Errorf("failed to create Tor dialer: %w", err) - } - conn, err := torDialer.Dial("tcp", targetAddr) - if err != nil { - return fmt.Errorf("failed to connect to relay target: %w", err) - } - defer conn.Close() - conn.SetDeadline(time.Now().Add(DeliveryTimeout)) - client, err := smtp.NewClient(conn, domain) - if err != nil { - return fmt.Errorf("failed to create SMTP client: %w", err) - } - defer client.Close() - if err := client.Mail(envelope.MessageFrom); err != nil { - return fmt.Errorf("MAIL FROM failed: %w", err) - } - if err := client.Rcpt(envelope.MessageTo); err != nil { - return fmt.Errorf("RCPT TO failed: %w", err) - } - wc, err := client.Data() - if err != nil { - return fmt.Errorf("DATA command failed: %w", err) - } - defer wc.Close() - if _, err := io.Copy(wc, envelope.MessageData); err != nil { - return fmt.Errorf("message transfer failed: %w", err) - } - return nil -} - -func queueEnvelope(envelope *Envelope) bool { - mailQueueMutex.Lock() - defer mailQueueMutex.Unlock() - select { - case mailQueue <- envelope: - return true - default: - return false - } -} - -func StartRelayWorkers(queue chan *Envelope, workerCount int) { - for i := 0; i < workerCount; i++ { - go func(id int) { - log.Printf("Relay worker %d started", id) - for env := range queue { - err := smtpRelay(env) - if err != nil { - domain := extractDomainFromAddress(env.MessageTo) - if isOnionDomain(domain) && env.RetryCount < 9 { - env.RetryCount++ - backoff := time.Duration(math.Pow(2, float64(env.RetryCount))) * time.Second - jitter := time.Duration(cryptoRandInt63n(int64(backoff / 2))) - time.Sleep(backoff + jitter) - queueEnvelope(env) - } - } - } - }(i) - } -} - -func main() { - listenAddr := flag.String("addr", "127.0.0.1:2525", "Listen address") - serverName := flag.String("name", "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuv.onion", "Server v3 .onion hostname") - flag.Parse() - if err := validateServerName(*serverName); err != nil { - log.Fatalf("Invalid server name: %v", err) - } - mailQueue = make(chan *Envelope, 100) - StartRelayWorkers(mailQueue, RelayWorkerCount) - messageIDCache := NewMessageIDCache() - rateLimiter := NewRateLimiter() - mixnetBatcher := NewMixnetBatcher(mailQueue) - coverTraffic := NewCoverTrafficGenerator(mailQueue) - server := &Server{ - Name: *serverName, - Addr: *listenAddr, - MessageIDCache: messageIDCache, - RateLimiter: rateLimiter, - MixnetBatcher: mixnetBatcher, - CoverTraffic: coverTraffic, - } - handler := &SimpleHandler{batcher: mixnetBatcher} - server.Handler = handler - log.Printf("Pluto2 SMTP Relay Server") - log.Printf("========================") - log.Printf("Server: %s", server.Name) - log.Printf("Listen: %s", server.Addr) - log.Printf("Tor proxy: %s", TorSocksProxyAddr) - log.Printf("") - log.Printf("Security: Replay protection, Rate limiting, Timing attacks, Size correlation, Mixnet batching, Cover traffic") - log.Printf("Validation: RFC-compliant email, v3 .onion server only") - log.Printf("Policy: FROM any valid, TO .onion + clearnet(MX), RELAY .onion only") - log.Printf("") - listener, err := net.Listen("tcp", server.Addr) - if err != nil { - log.Fatalf("Failed to create listener: %v", err) - } - if err := server.Serve(listener); err != nil { - log.Fatalf("Server failed: %v", err) - } -} |
