// signalProcessor is a goroutine that handles OS signals func signalProcessor(c <-chan os.Signal) { for { sig := <-c switch sig { case syscall.SIGHUP: // Rotate logs if configured if logf != nil { log.LogInfo("Recieved SIGHUP, cycling logfile") closeLogFile() openLogFile() } else { log.LogInfo("Ignoring SIGHUP, logfile not configured") } case syscall.SIGTERM: // Initiate shutdown log.LogInfo("Received SIGTERM, shutting down") go timedExit() web.Stop() if smtpServer != nil { smtpServer.Stop() } else { log.LogError("smtpServer was nil during shutdown") } } } }
// Initialize websocket from incus func setupWebSocket(cfg config.WebConfig, ds *data.DataStore) { mymap := make(map[string]string) mymap["client_broadcasts"] = strconv.FormatBool(cfg.ClientBroadcasts) mymap["connection_timeout"] = strconv.Itoa(cfg.ConnTimeout) mymap["redis_enabled"] = strconv.FormatBool(cfg.RedisEnabled) mymap["debug"] = "true" conf := incus.InitConfig(mymap) store := incus.InitStore(&conf) Websocket = incus.CreateServer(&conf, store) log.LogInfo("Incus Websocket Init") go func() { for { select { case msg := <-ds.NotifyMailChan: go Websocket.AppListener(msg) } } }() go Websocket.RedisListener() go Websocket.SendHeartbeats() }
// Start() the web server 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.LogInfo("HTTP listening on TCP4 %v", addr) var err error listener, err = net.Listen("tcp", addr) if err != nil { log.LogError("HTTP failed to start TCP4 listener: %v", err) // TODO More graceful early-shutdown procedure panic(err) } err = server.Serve(listener) if shutdown { log.LogTrace("HTTP server shutting down on request") } else if err != nil { log.LogError("HTTP server failed: %v", err) } }
func (ds *DataStore) StorageConnect() { if ds.Config.Storage == "mongodb" { log.LogInfo("Trying MongoDB storage") s := CreateMongoDB(ds.Config) if s == nil { log.LogInfo("MongoDB storage unavailable") } else { log.LogInfo("Using MongoDB storage") ds.Storage = s } // start some savemail workers for i := 0; i < 3; i++ { go ds.SaveMail() } } }
func setupRoutes(cfg config.WebConfig) { log.LogInfo("Theme templates mapped to '%v'", cfg.TemplateDir) log.LogInfo("Theme static content mapped to '%v'", cfg.PublicDir) r := mux.NewRouter() // Static content r.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir(cfg.PublicDir)))) // Register a couple of routes r.Path("/").Handler(handler(Home)).Name("Home").Methods("GET") r.Path("/status").Handler(handler(Status)).Name("Status").Methods("GET") // Mail r.Path("/mails").Handler(handler(MailList)).Name("Mails").Methods("GET") r.Path("/mails/{page:[0-9]+}").Handler(handler(MailList)).Name("MailList").Methods("GET") r.Path("/mail/{id:[0-9a-z]+}").Handler(handler(MailView)).Name("MailView").Methods("GET") r.Path("/mail/attachment/{id:[0-9a-z]+}/{[*.*]}").Handler(handler(MailAttachment)).Name("MailAttachment").Methods("GET") r.Path("/mail/delete/{id:[0-9a-z]+}").Handler(handler(MailDelete)).Name("MailDelete").Methods("GET") // Login r.Path("/login").Handler(handler(Login)).Methods("POST") r.Path("/login").Handler(handler(LoginForm)).Name("Login").Methods("GET") r.Path("/logout").Handler(handler(Logout)).Name("Logout").Methods("GET") r.Path("/register").Handler(handler(Register)).Methods("POST") r.Path("/register").Handler(handler(RegisterForm)).Name("Register").Methods("GET") // Add to greylist r.Path("/greylist/host/{id:[0-9a-z]+}").Handler(handler(MailView)).Name("GreyHostAdd").Methods("GET") r.Path("/greylist/mailfrom/{id:[0-9a-z]+}").Handler(handler(GreyMailFromAdd)).Name("GreyMailFromAdd").Methods("GET") r.Path("/greylist/tomail/{id:[0-9a-z]+}").Handler(handler(GreyMailFromAdd)).Name("GreyMailToAdd").Methods("GET") // Nginx Xclient auth r.Path("/auth-smtp").Handler(handler(NginxHTTPAuth)).Name("Nginx") r.Path("/ping").Handler(handler(Ping)).Name("Ping").Methods("GET") // Web-Socket & Fallback longpoll r.HandleFunc("/ws/", Websocket.SocketListener) r.HandleFunc("/lp", Websocket.LongPollListener) Router = r // Send all incoming requests to router. http.Handle("/", Router) }
func (s *Server) handleClient(c *Client) { log.LogInfo("SMTP Connection from %v, starting session <%v>", c.conn.RemoteAddr(), c.id) defer func() { s.closeClient(c) s.waitgroup.Done() }() c.greet() // check if client on trusted hosts if s.trustedHosts[net.ParseIP(c.remoteHost).String()] { c.logInfo("Remote Client is Trusted: <%s>", c.remoteHost) c.trusted = true } // This is our command reading loop for i := 0; i < 100; i++ { if c.state == 2 { // Special case, does not use SMTP command format c.processData() continue } line, err := c.readLine() if err == nil { if cmd, arg, ok := c.parseCmd(line); ok { c.handle(cmd, arg, line) } } else { // readLine() returned an error if err == io.EOF { c.logWarn("Got EOF while in state %v", c.state) break } // not an EOF c.logWarn("Connection error: %v", err) if neterr, ok := err.(net.Error); ok && neterr.Timeout() { c.Write("221", "Idle timeout, bye bye") break } c.Write("221", "Connection error, sorry") break } if c.kill_time > 1 || c.errors > 3 { return } } c.logInfo("Closing connection") }
func main() { flag.Parse() runtime.GOMAXPROCS(runtime.NumCPU()) if *help { flag.Usage() return } // Load & Parse config /* if flag.NArg() != 1 { flag.Usage() os.Exit(1) }*/ //err := config.LoadConfig(flag.Arg(0)) err := config.LoadConfig(*configfile) if err != nil { fmt.Fprintf(os.Stderr, "Failed to parse config: %v\n", err) os.Exit(1) } // Setup signal handler sigChan := make(chan os.Signal) signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGTERM) go signalProcessor(sigChan) // Configure logging, close std* fds level, _ := config.Config.String("logging", "level") log.SetLogLevel(level) if *logfile != "stderr" { // stderr is the go logging default if *logfile == "stdout" { // set to stdout golog.SetOutput(os.Stdout) } else { err := openLogFile() if err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(1) } defer closeLogFile() // close std* streams os.Stdout.Close() os.Stderr.Close() // Warning: this will hide panic() output os.Stdin.Close() os.Stdout = logf os.Stderr = logf } } log.LogInfo("Smtpd %v (%v) starting...", VERSION, BUILD_DATE) // Write pidfile if requested // TODO: Probably supposed to remove pidfile during shutdown if *pidfile != "none" { pidf, err := os.Create(*pidfile) if err != nil { log.LogError("Failed to create %v: %v", *pidfile, err) os.Exit(1) } defer pidf.Close() fmt.Fprintf(pidf, "%v\n", os.Getpid()) } // Grab our datastore ds := data.NewDataStore() // Start HTTP server //web.Initialize(config.GetWebConfig(), ds) //go web.Start() // Startup SMTP server, block until it exits smtpServer = smtpd.NewSmtpServer(config.GetSmtpConfig(), ds) smtpServer.Start() // Wait for active connections to finish smtpServer.Drain() }
func (c *Client) logInfo(msg string, args ...interface{}) { log.LogInfo("SMTP[%v]<%v> %v", c.remoteHost, c.id, fmt.Sprintf(msg, args...)) }
// Main listener loop func (s *Server) Start() { cfg := config.GetSmtpConfig() log.LogTrace("Loading the certificate: %s", cfg.PubKey) cert, err := tls.LoadX509KeyPair(cfg.PubKey, cfg.PrvKey) if err != nil { log.LogError("There was a problem with loading the certificate: %s", err) } else { s.TLSConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.VerifyClientCertIfGiven, ServerName: cfg.Domain, } //s.TLSConfig .Rand = rand.Reader } defer s.Stop() addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%v:%v", cfg.Ip4address, cfg.Ip4port)) if err != nil { log.LogError("Failed to build tcp4 address: %v", err) // TODO More graceful early-shutdown procedure //panic(err) s.Stop() return } // Start listening for SMTP connections log.LogInfo("SMTP listening on TCP4 %v", addr) s.listener, err = net.ListenTCP("tcp4", addr) if err != nil { log.LogError("SMTP failed to start tcp4 listener: %v", err) // TODO More graceful early-shutdown procedure //panic(err) s.Stop() return } //Connect database s.Store.StorageConnect() var tempDelay time.Duration var clientId int64 // Handle incoming connections for clientId = 1; ; clientId++ { if conn, err := s.listener.Accept(); err != nil { if nerr, ok := err.(net.Error); ok && nerr.Temporary() { // Temporary error, sleep for a bit and try again if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } log.LogError("SMTP accept error: %v; retrying in %v", err, tempDelay) time.Sleep(tempDelay) continue } else { if s.shutdown { log.LogTrace("SMTP listener shutting down on request") return } // TODO Implement a max error counter before shutdown? // or maybe attempt to restart smtpd panic(err) } } else { tempDelay = 0 s.waitgroup.Add(1) log.LogInfo("There are now %s serving goroutines", strconv.Itoa(runtime.NumGoroutine())) host, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) s.sem <- 1 // Wait for active queue to drain. go s.handleClient(&Client{ state: 1, server: s, conn: conn, remoteHost: host, time: time.Now().Unix(), bufin: bufio.NewReader(conn), bufout: bufio.NewWriter(conn), id: clientId, }) } } }