1package main
2
3import (
4 "fmt"
5 "os"
6 "time"
7
8 "github.com/mjl-/mox/dmarcdb"
9 "github.com/mjl-/mox/dns"
10 "github.com/mjl-/mox/http"
11 "github.com/mjl-/mox/imapserver"
12 "github.com/mjl-/mox/mlog"
13 "github.com/mjl-/mox/mox-"
14 "github.com/mjl-/mox/mtastsdb"
15 "github.com/mjl-/mox/queue"
16 "github.com/mjl-/mox/smtpserver"
17 "github.com/mjl-/mox/store"
18 "github.com/mjl-/mox/tlsrptdb"
19 "github.com/mjl-/mox/tlsrptsend"
20)
21
22func shutdown(log mlog.Log) {
23 // We indicate we are shutting down. Causes new connections and new SMTP commands
24 // to be rejected. Should stop active connections pretty quickly.
25 mox.ShutdownCancel()
26
27 // Now we are going to wait for all connections to be gone, up to a timeout.
28 done := mox.Connections.Done()
29 second := time.Tick(time.Second)
30 select {
31 case <-done:
32 log.Print("connections shutdown, waiting until 1 second passed")
33 <-second
34
35 case <-time.Tick(3 * time.Second):
36 // We now cancel all pending operations, and set an immediate deadline on sockets.
37 // Should get us a clean shutdown relatively quickly.
38 mox.ContextCancel()
39 mox.Connections.Shutdown()
40
41 second := time.Tick(time.Second)
42 select {
43 case <-done:
44 log.Print("no more connections, shutdown is clean, waiting until 1 second passed")
45 <-second // Still wait for second, giving processes like imports a chance to clean up.
46 case <-second:
47 log.Print("shutting down with pending sockets")
48 }
49 }
50 err := os.Remove(mox.DataDirPath("ctl"))
51 log.Check(err, "removing ctl unix domain socket during shutdown")
52}
53
54// start initializes all packages, starts all listeners and the switchboard
55// goroutine, then returns.
56func start(mtastsdbRefresher, sendDMARCReports, sendTLSReports, skipForkExec bool) error {
57 smtpserver.Listen()
58 imapserver.Listen()
59 http.Listen()
60
61 if !skipForkExec {
62 // If we were just launched as root, fork and exec as unprivileged user, handing
63 // over the bound sockets to the new process. We'll get to this same code path
64 // again, skipping this if block, continuing below with the actual serving.
65 if os.Getuid() == 0 {
66 mox.ForkExecUnprivileged()
67 panic("cannot happen")
68 } else {
69 mox.CleanupPassedFiles()
70 }
71 }
72
73 if err := mtastsdb.Init(mtastsdbRefresher); err != nil {
74 return fmt.Errorf("mtasts init: %s", err)
75 }
76
77 if err := tlsrptdb.Init(); err != nil {
78 return fmt.Errorf("tlsrpt init: %s", err)
79 }
80
81 done := make(chan struct{}) // Goroutines for messages and webhooks, and cleaners.
82 if err := queue.Start(dns.StrictResolver{Pkg: "queue"}, done); err != nil {
83 return fmt.Errorf("queue start: %s", err)
84 }
85
86 // dmarcdb starts after queue because it may start sending reports through the queue.
87 if err := dmarcdb.Init(); err != nil {
88 return fmt.Errorf("dmarc init: %s", err)
89 }
90 if sendDMARCReports {
91 dmarcdb.Start(dns.StrictResolver{Pkg: "dmarcdb"})
92 }
93
94 if sendTLSReports {
95 tlsrptsend.Start(dns.StrictResolver{Pkg: "tlsrptsend"})
96 }
97
98 store.StartAuthCache()
99 smtpserver.Serve()
100 imapserver.Serve()
101 http.Serve()
102
103 go func() {
104 store.Switchboard()
105 <-make(chan struct{})
106 }()
107 return nil
108}
109