1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package imapsql2021import (22 "context"23 "runtime/trace"2425 "github.com/emersion/go-imap"26 "github.com/emersion/go-imap/backend"27 "github.com/emersion/go-message/textproto"28 "github.com/emersion/go-smtp"29 imapsql "github.com/foxcpp/go-imap-sql"30 "github.com/foxcpp/maddy/framework/buffer"31 "github.com/foxcpp/maddy/framework/exterrors"32 "github.com/foxcpp/maddy/framework/module"33 "github.com/foxcpp/maddy/internal/target"34)3536type addedRcpt struct {37 rcptTo string38}39type delivery struct {40 store *Storage41 msgMeta *module.MsgMetadata42 d imapsql.Delivery43 mailFrom string4445 addedRcpts map[string]addedRcpt46}4748func (d *delivery) String() string {49 return d.store.Name() + ":" + d.store.InstanceName()50}5152func userDoesNotExist(actual error) error {53 return &exterrors.SMTPError{54 Code: 501,55 EnhancedCode: exterrors.EnhancedCode{5, 1, 1},56 Message: "User does not exist",57 TargetName: "imapsql",58 Err: actual,59 }60}6162func (d *delivery) AddRcpt(ctx context.Context, rcptTo string, _ smtp.RcptOptions) error {63 defer trace.StartRegion(ctx, "sql/AddRcpt").End()6465 accountName, err := d.store.deliveryNormalize(ctx, rcptTo)66 if err != nil {67 return userDoesNotExist(err)68 }6970 if _, ok := d.addedRcpts[accountName]; ok {71 return nil72 }7374 // This header is added to the message only for that recipient.75 // go-imap-sql does certain optimizations to store the message76 // with small amount of per-recipient data in a efficient way.77 userHeader := textproto.Header{}78 userHeader.Add("Delivered-To", accountName)7980 if err := d.d.AddRcpt(accountName, userHeader); err != nil {81 if err == imapsql.ErrUserDoesntExists || err == backend.ErrNoSuchMailbox {82 return userDoesNotExist(err)83 }84 if _, ok := err.(imapsql.SerializationError); ok {85 return &exterrors.SMTPError{86 Code: 453,87 EnhancedCode: exterrors.EnhancedCode{4, 3, 2},88 Message: "Internal server error, try again later",89 TargetName: "imapsql",90 Err: err,91 }92 }93 return err94 }9596 d.addedRcpts[accountName] = addedRcpt{97 rcptTo: rcptTo,98 }99 return nil100}101102func (d *delivery) Body(ctx context.Context, header textproto.Header, body buffer.Buffer) error {103 defer trace.StartRegion(ctx, "sql/Body").End()104105 if !d.msgMeta.Quarantine && d.store.filters != nil {106 for rcpt, rcptData := range d.addedRcpts {107 folder, flags, err := d.store.filters.IMAPFilter(rcpt, rcptData.rcptTo, d.msgMeta, header, body)108 if err != nil {109 d.store.Log.Error("IMAPFilter failed", err, "rcpt", rcpt)110 continue111 }112 d.d.UserMailbox(rcpt, folder, flags)113 }114 }115116 if d.msgMeta.Quarantine {117 if err := d.d.SpecialMailbox(imap.JunkAttr, d.store.junkMbox); err != nil {118 if _, ok := err.(imapsql.SerializationError); ok {119 return &exterrors.SMTPError{120 Code: 453,121 EnhancedCode: exterrors.EnhancedCode{4, 3, 2},122 Message: "Storage access serialiation problem, try again later",123 TargetName: "imapsql",124 Err: err,125 }126 }127 return err128 }129 }130131 header = header.Copy()132 header.Add("Return-Path", "<"+target.SanitizeForHeader(d.mailFrom)+">")133 err := d.d.BodyParsed(header, body.Len(), body)134 if _, ok := err.(imapsql.SerializationError); ok {135 return &exterrors.SMTPError{136 Code: 453,137 EnhancedCode: exterrors.EnhancedCode{4, 3, 2},138 Message: "Storage access serialiation problem, try again later",139 TargetName: "imapsql",140 Err: err,141 }142 }143 return err144}145146func (d *delivery) Abort(ctx context.Context) error {147 defer trace.StartRegion(ctx, "sql/Abort").End()148149 return d.d.Abort()150}151152func (d *delivery) Commit(ctx context.Context) error {153 defer trace.StartRegion(ctx, "sql/Commit").End()154155 return d.d.Commit()156}157158func (store *Storage) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) {159 defer trace.StartRegion(ctx, "sql/Start").End()160161 return &delivery{162 store: store,163 msgMeta: msgMeta,164 mailFrom: mailFrom,165 d: store.Back.NewDelivery(),166 addedRcpts: map[string]addedRcpt{},167 }, nil168}