// StartRetentionScanner launches a go-routine that scans for expired // messages, following the configured interval func StartRetentionScanner(ds DataStore, shutdownChannel chan bool) { globalShutdown = shutdownChannel retentionShutdown = make(chan bool) cfg := config.GetDataStoreConfig() expRetentionPeriod.Set(int64(cfg.RetentionMinutes * 60)) if cfg.RetentionMinutes > 0 { // Retention scanning enabled log.Infof("Retention configured for %v minutes", cfg.RetentionMinutes) go retentionScanner(ds, time.Duration(cfg.RetentionMinutes)*time.Minute, time.Duration(cfg.RetentionSleep)*time.Millisecond) } else { log.Infof("Retention scanner disabled") close(retentionShutdown) } }
// Start begins listening for HTTP requests func Start() { addr := fmt.Sprintf("%v:%v", webConfig.IP4address, webConfig.IP4port) server = &http.Server{ Addr: addr, Handler: nil, ReadTimeout: 60 * time.Second, WriteTimeout: 60 * time.Second, } // We don't use ListenAndServe because it lacks a way to close the listener log.Infof("HTTP listening on TCP4 %v", addr) var err error listener, err = net.Listen("tcp", addr) if err != nil { log.Errorf("HTTP failed to start TCP4 listener: %v", err) emergencyShutdown() return } // Listener go routine go serve() // Wait for shutdown select { case _ = <-globalShutdown: log.Tracef("HTTP server shutting down on request") } // Closing the listener will cause the serve() go routine to exit if err := listener.Close(); err != nil { log.Errorf("Failed to close HTTP listener: %v", err) } }
// Start the listener and handle incoming connections func (s *Server) Start() { cfg := config.GetSMTPConfig() addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%v:%v", cfg.IP4address, cfg.IP4port)) if err != nil { log.Errorf("Failed to build tcp4 address: %v", err) // serve() never called, so we do local shutdown here close(s.localShutdown) s.emergencyShutdown() return } log.Infof("SMTP listening on TCP4 %v", addr) s.listener, err = net.ListenTCP("tcp4", addr) if err != nil { log.Errorf("SMTP failed to start tcp4 listener: %v", err) // serve() never called, so we do local shutdown here close(s.localShutdown) s.emergencyShutdown() return } if !s.storeMessages { log.Infof("Load test mode active, messages will not be stored") } else if s.domainNoStore != "" { log.Infof("Messages sent to domain '%v' will be discarded", s.domainNoStore) } // Start retention scanner StartRetentionScanner(s.dataStore, s.globalShutdown) // Listener go routine go s.serve() // Wait for shutdown select { case _ = <-s.globalShutdown: log.Tracef("SMTP shutdown requested, connections will be drained") } // Closing the listener will cause the serve() go routine to exit if err := s.listener.Close(); err != nil { log.Errorf("Failed to close SMTP listener: %v", err) } }
// Initialize sets up things for unit tests or the Start() method func Initialize(cfg config.WebConfig, ds smtpd.DataStore, shutdownChan chan bool) { webConfig = cfg globalShutdown = shutdownChan // NewContext() will use this DataStore for the web handlers DataStore = ds // Content Paths log.Infof("HTTP templates mapped to %q", cfg.TemplateDir) log.Infof("HTTP static content mapped to %q", cfg.PublicDir) Router.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir(cfg.PublicDir)))) http.Handle("/", Router) // Session cookie setup if cfg.CookieAuthKey == "" { log.Infof("HTTP generating random cookie.auth.key") sessionStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(64)) } else { log.Tracef("HTTP using configured cookie.auth.key") sessionStore = sessions.NewCookieStore([]byte(cfg.CookieAuthKey)) } }
// NewMessage creates a new FileMessage object and sets the Date and Id fields. // It will also delete messages over messageCap if configured. func (mb *FileMailbox) NewMessage() (Message, error) { // Load index if !mb.indexLoaded { if err := mb.readIndex(); err != nil { return nil, err } } // Delete old messages over messageCap if mb.store.messageCap > 0 { for len(mb.messages) >= mb.store.messageCap { log.Infof("Mailbox %q over configured message cap", mb.name) if err := mb.messages[0].Delete(); err != nil { log.Errorf("Error deleting message: %s", err) } } } date := time.Now() id := generateID(date) return &FileMessage{mailbox: mb, Fid: id, Fdate: date, writable: true}, nil }
// Start the server and listen for connections func (s *Server) Start() { cfg := config.GetPOP3Config() addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%v:%v", cfg.IP4address, cfg.IP4port)) if err != nil { log.Errorf("POP3 Failed to build tcp4 address: %v", err) // serve() never called, so we do local shutdown here close(s.localShutdown) s.emergencyShutdown() return } log.Infof("POP3 listening on TCP4 %v", addr) s.listener, err = net.ListenTCP("tcp4", addr) if err != nil { log.Errorf("POP3 failed to start tcp4 listener: %v", err) // serve() never called, so we do local shutdown here close(s.localShutdown) s.emergencyShutdown() return } // Listener go routine go s.serve() // Wait for shutdown select { case _ = <-s.globalShutdown: } log.Tracef("POP3 shutdown requested, connections will be drained") // Closing the listener will cause the serve() go routine to exit if err := s.listener.Close(); err != nil { log.Errorf("Error closing POP3 listener: %v", err) } }
func (ss *Session) logInfo(msg string, args ...interface{}) { log.Infof("SMTP[%v]<%v> %v", ss.remoteHost, ss.id, fmt.Sprintf(msg, args...)) }
/* Session flow: * 1. Send initial greeting * 2. Receive cmd * 3. If good cmd, respond, optionally change state * 4. If bad cmd, respond error * 5. Goto 2 */ func (s *Server) startSession(id int, conn net.Conn) { log.Infof("SMTP Connection from %v, starting session <%v>", conn.RemoteAddr(), id) expConnectsCurrent.Add(1) defer func() { if err := conn.Close(); err != nil { log.Errorf("Error closing connection for <%v>: %v", id, err) } s.waitgroup.Done() expConnectsCurrent.Add(-1) }() ss := NewSession(s, id, conn) ss.greet() // This is our command reading loop for ss.state != QUIT && ss.sendError == nil { if ss.state == DATA { // Special case, does not use SMTP command format ss.dataHandler() continue } line, err := ss.readLine() if err == nil { if cmd, arg, ok := ss.parseCmd(line); ok { // Check against valid SMTP commands if cmd == "" { ss.send("500 Speak up") continue } if !commands[cmd] { ss.send(fmt.Sprintf("500 Syntax error, %v command unrecognized", cmd)) ss.logWarn("Unrecognized command: %v", cmd) continue } // Commands we handle in any state switch cmd { case "SEND", "SOML", "SAML", "EXPN", "HELP", "TURN": // These commands are not implemented in any state ss.send(fmt.Sprintf("502 %v command not implemented", cmd)) ss.logWarn("Command %v not implemented by Inbucket", cmd) continue case "VRFY": ss.send("252 Cannot VRFY user, but will accept message") continue case "NOOP": ss.send("250 I have sucessfully done nothing") continue case "RSET": // Reset session ss.logTrace("Resetting session state on RSET request") ss.reset() ss.send("250 Session reset") continue case "QUIT": ss.send("221 Goodnight and good luck") ss.enterState(QUIT) continue } // Send command to handler for current state switch ss.state { case GREET: ss.greetHandler(cmd, arg) continue case READY: ss.readyHandler(cmd, arg) continue case MAIL: ss.mailHandler(cmd, arg) continue } ss.logError("Session entered unexpected state %v", ss.state) break } else { ss.send("500 Syntax error, command garbled") } } else { // readLine() returned an error if err == io.EOF { switch ss.state { case GREET, READY: // EOF is common here ss.logInfo("Client closed connection (state %v)", ss.state) default: ss.logWarn("Got EOF while in state %v", ss.state) } break } // not an EOF ss.logWarn("Connection error: %v", err) if netErr, ok := err.(net.Error); ok { if netErr.Timeout() { ss.send("221 Idle timeout, bye bye") break } } ss.send("221 Connection error, sorry") break } } if ss.sendError != nil { ss.logWarn("Network send error: %v", ss.sendError) } ss.logInfo("Closing connection") }