aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUGS2
-rw-r--r--config.go48
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--incoming.go8
-rw-r--r--maild.scfg7
-rw-r--r--main.go6
-rw-r--r--mta_recv.go38
8 files changed, 66 insertions, 49 deletions
diff --git a/BUGS b/BUGS
new file mode 100644
index 0000000..3f83851
--- /dev/null
+++ b/BUGS
@@ -0,0 +1,2 @@
+There might be standard library functions that use runes instead of bytes while
+operating over strings.
diff --git a/config.go b/config.go
index 8ba30ad..f187b69 100644
--- a/config.go
+++ b/config.go
@@ -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()
}
diff --git a/go.mod b/go.mod
index 5ea39e0..314be8d 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 1d1725c..3e0ed5e 100644
--- a/go.sum
+++ b/go.sum
@@ -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)
diff --git a/maild.scfg b/maild.scfg
index 84c613e..99efe9c 100644
--- a/maild.scfg
+++ b/maild.scfg
@@ -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
+}
diff --git a/main.go b/main.go
index f71391d..94f195e 100644
--- a/main.go
+++ b/main.go
@@ -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