diff options
author | Runxi Yu <me@runxiyu.org> | 2025-01-12 04:38:25 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-01-12 04:42:09 +0800 |
commit | 27cf6309af4536be615a34a647bbc54e6f8f43c6 (patch) | |
tree | 7563339698b24510f1024c4904f5a55ff7614c03 | |
parent | Update README (diff) | |
download | maild-27cf6309af4536be615a34a647bbc54e6f8f43c6.tar.gz maild-27cf6309af4536be615a34a647bbc54e6f8f43c6.tar.zst maild-27cf6309af4536be615a34a647bbc54e6f8f43c6.zip |
Add basic routing
-rw-r--r-- | BUGS | 2 | ||||
-rw-r--r-- | config.go | 48 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | incoming.go | 8 | ||||
-rw-r--r-- | maild.scfg | 7 | ||||
-rw-r--r-- | main.go | 6 | ||||
-rw-r--r-- | mta_recv.go | 38 |
8 files changed, 66 insertions, 49 deletions
@@ -0,0 +1,2 @@ +There might be standard library functions that use runes instead of bytes while +operating over strings. @@ -3,16 +3,14 @@ package main import ( "bufio" "os" - "reflect" "sync" - "go.lindenii.runxiyu.org/lindenii-common/misc" "go.lindenii.runxiyu.org/lindenii-common/scfg" ) var config struct { - Server_name string `scfg:"server_name"` - Inbox_path string `scfg:"inbox_path"` + Server_name string `scfg:"server_name"` + Routes map[string]string `scfg:"routes"` } var config_mutex sync.RWMutex @@ -36,34 +34,20 @@ func load_config(path string) error { return nil } -var directories struct { - inbox misc.Dir_t +// config_fetch_one fetches one value from the configuration. +// +// Consequtive calls do not guarantee a consistent snapshot of the +// configuration. Use config_consistent_run in these cases. +func config_fetch_one[T any](x *T) T { + config_mutex.RLock() + defer config_mutex.RUnlock() + return *x } -var directories_mutex sync.RWMutex -func prepare_dirs() error { - directories_mutex.Lock() - defer directories_mutex.Unlock() - var err error - directories.inbox, err = misc.Open_directory_readonly(config.Inbox_path) - if err != nil { - return err - } - return nil -} - -// clean_up_dirs closes all directories. It should only be called before -// program exit. -func clean_up_dirs() { - directories_mutex.Lock() - defer directories_mutex.Unlock() - directories_rv := reflect.ValueOf(directories) - directories_rt := directories_rv.Type() - for i := range directories_rt.NumField() { - dir, ok := directories_rv.Field(i).Interface().(misc.Dir_t) - if !ok { - panic("directories contains a field that's not misc.Dir_t") - } - dir.Close() - } +// config_consistent_run runs the supplied function with a consistent snapshot +// of the configuration. +func config_consistent_run(f func()) { + config_mutex.RLock() + defer config_mutex.RUnlock() + f() } @@ -2,4 +2,4 @@ module go.lindenii.runxiyu.org/maild go 1.23.4 -require go.lindenii.runxiyu.org/lindenii-common v0.0.0-20250104140324-f57ba68adb4d +require go.lindenii.runxiyu.org/lindenii-common v0.0.0-20250111202249-35d69905e2fc @@ -1,2 +1,2 @@ -go.lindenii.runxiyu.org/lindenii-common v0.0.0-20250104140324-f57ba68adb4d h1:ilNgswXXIKwDK/8xvV0UH6FNZ1XpZHxm3s381xz2icM= -go.lindenii.runxiyu.org/lindenii-common v0.0.0-20250104140324-f57ba68adb4d/go.mod h1:bOxuuGXA3UpbLb1lKohr2j2MVcGGLcqfAprGx9VCkMA= +go.lindenii.runxiyu.org/lindenii-common v0.0.0-20250111202249-35d69905e2fc h1:jYjxxslcbZSKmXT96FhRW//t0dx9LN54J4PeD6oXTQk= +go.lindenii.runxiyu.org/lindenii-common v0.0.0-20250111202249-35d69905e2fc/go.mod h1:bOxuuGXA3UpbLb1lKohr2j2MVcGGLcqfAprGx9VCkMA= diff --git a/incoming.go b/incoming.go index 35aa006..e672587 100644 --- a/incoming.go +++ b/incoming.go @@ -8,7 +8,7 @@ import ( "go.lindenii.runxiyu.org/lindenii-common/misc" ) -func deliver_incoming(envelope_from string, envelope_recipients []string, data []byte) error { +func deliver_incoming(envelope_from string, envelope_recipients []string, data []byte, dir_path string) error { clog.Debug( "incoming_mail", "envelope_from", envelope_from, @@ -16,7 +16,11 @@ func deliver_incoming(envelope_from string, envelope_recipients []string, data [ "data", string(data), ) t := time.Now() - fd, err := misc.Open_file_at(directories.inbox, envelope_from+" "+t.Format(time.RFC3339Nano)+".eml", os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) + dir, err := misc.Open_directory_readonly(dir_path) + if err != nil { + return misc.Wrap_one_error(err_deliver_write, err) + } + fd, err := misc.Open_file_at(dir, envelope_from+" "+t.Format(time.RFC3339Nano)+".eml", os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) if err != nil { // TODO: handle fs.ErrExist return misc.Wrap_one_error(err_deliver_write, err) @@ -1,2 +1,7 @@ server_name mail.andrewyu.org -inbox_path /home/runxiyu/inbox + +routes { + andrew@andrewyu.org /home/runxiyu/inbox + test@andrewyu.org /home/runxiyu/inbox + adeline@andrewyu.org /home/runxiyu/inbox +} @@ -19,12 +19,6 @@ func main() { panic(err) } - err = prepare_dirs() - if err != nil { - panic(err) - } - defer clean_up_dirs() - listener, err := net.Listen("tcp", ":25") if err != nil { panic(err) diff --git a/mta_recv.go b/mta_recv.go index fe6139f..d5a14ed 100644 --- a/mta_recv.go +++ b/mta_recv.go @@ -5,6 +5,8 @@ import ( "bytes" "slices" "strings" + + "go.lindenii.runxiyu.org/lindenii-common/mailkit" ) type server_state_t uint @@ -17,7 +19,13 @@ const ( ) func handle_incoming_server_connection(reader *bufio.Reader, writer *bufio.Writer) error { - _, _ = writer.WriteString("220 " + config.Server_name + " " + VERSION + "\r\n") + var my_server_name string + var routes map[string]string + config_consistent_run(func() { + my_server_name = config.Server_name + routes = config.Routes + }) + _, _ = writer.WriteString("220 " + my_server_name + " " + VERSION + "\r\n") _ = writer.Flush() server_state := server_state_begin var remote_server_name string @@ -51,7 +59,7 @@ func handle_incoming_server_connection(reader *bufio.Reader, writer *bufio.Write remote_server_name = param _ = remote_server_name // TODO server_state = server_state_helo - _, _ = writer.WriteString("250 " + config.Server_name + "\r\n") + _, _ = writer.WriteString("250 " + my_server_name + "\r\n") _ = writer.Flush() case "MAIL": switch server_state { @@ -88,7 +96,14 @@ func handle_incoming_server_connection(reader *bufio.Reader, writer *bufio.Write _ = writer.Flush() break } - current_rcpt_to = append(current_rcpt_to, param[len("TO:"):]) + recipient, _, _ := mailkit.Strip_angle_brackets(param[len("TO:"):]) + _, ok := routes[recipient] + if !ok { + _, _ = writer.WriteString("550 5.1.1 <" + recipient + ">: Recipient address rejected: User unknown in local recipient table\r\n") + _ = writer.Flush() + break switch_cmd + } + current_rcpt_to = append(current_rcpt_to, recipient) server_state = server_state_rcpt _, _ = writer.WriteString("250 2.1.5 Ok\r\n") _ = writer.Flush() @@ -129,11 +144,24 @@ func handle_incoming_server_connection(reader *bufio.Reader, writer *bufio.Write if err != nil { return err } - err = deliver_incoming(current_mail_from, current_rcpt_to, current_data) + { + inboxes_to_deliver_to := make(map[string]struct{}) + for _, recipient := range current_rcpt_to { + inbox, ok := routes[recipient] + if !ok { + _, _ = writer.WriteString("550 5.1.1 <" + recipient + ">: Recipient address rejected: User unknown in local recipient table\r\n") + break switch_cmd + } + inboxes_to_deliver_to[inbox] = struct{}{} + } + for inbox := range inboxes_to_deliver_to { + err = deliver_incoming(current_mail_from, current_rcpt_to, current_data, inbox) + } + } if err == nil { _, _ = writer.WriteString("250 2.0.0 Ok: Accepted\r\n") } else { - _, _ = writer.WriteString("500 2.0.0 Error: "+err.Error()+"\r\n") + _, _ = writer.WriteString("500 2.0.0 Error: " + err.Error() + "\r\n") } _ = writer.Flush() server_state = server_state_helo |