Add LMTP server
This commit is contained in:
parent
18c2f95112
commit
3867fa36a2
9 changed files with 187 additions and 35 deletions
49
README.md
49
README.md
|
|
@ -6,7 +6,7 @@ An open-source email deliverability testing platform that analyzes test emails a
|
||||||
|
|
||||||
- **Complete Email Analysis**: Analyzes SPF, DKIM, DMARC, SpamAssassin scores, DNS records, blacklist status, content quality, and more
|
- **Complete Email Analysis**: Analyzes SPF, DKIM, DMARC, SpamAssassin scores, DNS records, blacklist status, content quality, and more
|
||||||
- **REST API**: Full-featured API for creating tests and retrieving reports
|
- **REST API**: Full-featured API for creating tests and retrieving reports
|
||||||
- **Email Receiver**: MDA (Mail Delivery Agent) mode for processing incoming test emails
|
- **LMTP Server**: Built-in LMTP server for seamless MTA integration
|
||||||
- **Scoring System**: 0-10 scoring with weighted factors across authentication, spam, blacklists, content, and headers
|
- **Scoring System**: 0-10 scoring with weighted factors across authentication, spam, blacklists, content, and headers
|
||||||
- **Database Storage**: SQLite or PostgreSQL support
|
- **Database Storage**: SQLite or PostgreSQL support
|
||||||
- **Configurable**: via environment or config file for all settings
|
- **Configurable**: via environment or config file for all settings
|
||||||
|
|
@ -90,54 +90,47 @@ happyDeliver will not perform thoses checks, it relies instead on standard softw
|
||||||
|
|
||||||
Choose one of the following way to integrate happyDeliver in your existing setup:
|
Choose one of the following way to integrate happyDeliver in your existing setup:
|
||||||
|
|
||||||
#### Postfix Transport rule
|
#### Postfix LMTP Transport
|
||||||
|
|
||||||
You'll obtains the best results with a custom [transport tule](https://www.postfix.org/transport.5.html).
|
You'll obtain the best results with a custom [transport rule](https://www.postfix.org/transport.5.html) using LMTP.
|
||||||
|
|
||||||
1. Append the following lines at the end of your `master.cf` file:
|
1. Start the happyDeliver server with LMTP enabled (default listens on `127.0.0.1:2525`):
|
||||||
|
|
||||||
```diff
|
```bash
|
||||||
+
|
./happyDeliver server
|
||||||
+# happyDeliver analyzer - receives emails matching transport_maps
|
|
||||||
+happydeliver unix - n n - - pipe
|
|
||||||
+ flags=DRXhu user=happydeliver argv=/path/to/happyDeliver analyze -recipient ${recipient}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Create the file `/etc/postfix/transport_happyDeliver` with the following content:
|
You can customize the LMTP address with the `-lmtp-addr` flag or in the config file.
|
||||||
|
|
||||||
|
2. Create the file `/etc/postfix/transport_happydeliver` with the following content:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Transport map - route test emails to happyDeliver analyzer
|
# Transport map - route test emails to happyDeliver LMTP server
|
||||||
# Pattern: test-<uuid>@yourdomain.com -> happydeliver pipe
|
# Pattern: test-<uuid>@yourdomain.com -> LMTP on localhost:2525
|
||||||
|
|
||||||
/^test-[a-f0-9-]+@yourdomain\.com$/ happydeliver:
|
/^test-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}@yourdomain\.com$/ lmtp:inet:127.0.0.1:2525
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Append the created file to `transport_maps` in your `main.cf`:
|
3. Append the created file to `transport_maps` in your `main.cf`:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
-transport_maps = texthash:/etc/postfix/transport
|
-transport_maps = texthash:/etc/postfix/transport
|
||||||
+transport_maps = texthash:/etc/postfix/transport, pcre:/etc/postfix/transport_maps
|
+transport_maps = texthash:/etc/postfix/transport, pcre:/etc/postfix/transport_happydeliver
|
||||||
```
|
```
|
||||||
|
|
||||||
If your `transport_maps` option is not set, just append this line:
|
If your `transport_maps` option is not set, just append this line:
|
||||||
|
|
||||||
```
|
```
|
||||||
transport_maps = pcre:/etc/postfix/transport_maps
|
transport_maps = pcre:/etc/postfix/transport_happydeliver
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: to use the `pcre:` type, you need to have `postfix-pcre` installed.
|
Note: to use the `pcre:` type, you need to have `postfix-pcre` installed.
|
||||||
|
|
||||||
#### Postfix Aliases
|
4. Reload Postfix configuration:
|
||||||
|
|
||||||
You can dedicate an alias to the tool using your `recipient_delimiter` (most likely `+`).
|
```bash
|
||||||
|
postfix reload
|
||||||
Add the following line in your `/etc/postfix/aliases`:
|
```
|
||||||
|
|
||||||
```diff
|
|
||||||
+test: "|/path/to/happyDeliver analyze"
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the recipient address has to be present in header.
|
|
||||||
|
|
||||||
#### 4. Create a Test
|
#### 4. Create a Test
|
||||||
|
|
||||||
|
|
@ -175,9 +168,9 @@ curl http://localhost:8080/api/report/550e8400-e29b-41d4-a716-446655440000
|
||||||
| `/api/report/{id}/raw` | GET | Get raw annotated email |
|
| `/api/report/{id}/raw` | GET | Get raw annotated email |
|
||||||
| `/api/status` | GET | Service health and status |
|
| `/api/status` | GET | Service health and status |
|
||||||
|
|
||||||
## Email Analyzer (MDA Mode)
|
## Email Analyzer (CLI Mode)
|
||||||
|
|
||||||
To process an email from an MTA pipe:
|
For manual testing or debugging, you can analyze emails from the command line:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat email.eml | ./happyDeliver analyze
|
cat email.eml | ./happyDeliver analyze
|
||||||
|
|
@ -189,6 +182,8 @@ Or specify recipient explicitly:
|
||||||
cat email.eml | ./happyDeliver analyze -recipient test-uuid@yourdomain.com
|
cat email.eml | ./happyDeliver analyze -recipient test-uuid@yourdomain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note:** In production, emails are delivered via LMTP (see integration instructions above).
|
||||||
|
|
||||||
## Scoring System
|
## Scoring System
|
||||||
|
|
||||||
The deliverability score is calculated from 0 to 10 based on:
|
The deliverability score is calculated from 0 to 10 based on:
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,3 @@ policy-spf unix - n n - 0 spawn
|
||||||
# SpamAssassin content filter
|
# SpamAssassin content filter
|
||||||
spamassassin unix - n n - - pipe
|
spamassassin unix - n n - - pipe
|
||||||
user=mail argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
|
user=mail argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
|
||||||
|
|
||||||
# happyDeliver analyzer - receives emails matching transport_maps
|
|
||||||
happydeliver unix - n n - - pipe
|
|
||||||
flags=DRXhu user=happydeliver argv=/usr/local/bin/happyDeliver analyze -config /etc/happydeliver/config.yaml -recipient ${recipient}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Transport map - route test emails to happyDeliver analyzer
|
# Transport map - route test emails to happyDeliver LMTP server
|
||||||
# Pattern: test-<uuid>@domain.com -> happydeliver pipe
|
# Pattern: test-<uuid>@domain.com -> LMTP on localhost:2525
|
||||||
|
|
||||||
/^test-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}@.*$/ happydeliver:
|
/^test-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}@.*$/ lmtp:inet:127.0.0.1:2525
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -3,6 +3,7 @@ module git.happydns.org/happyDeliver
|
||||||
go 1.24.6
|
go 1.24.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/emersion/go-smtp v0.24.0
|
||||||
github.com/getkin/kin-openapi v0.132.0
|
github.com/getkin/kin-openapi v0.132.0
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
|
@ -19,6 +20,7 @@ require (
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -17,6 +17,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
|
||||||
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
|
github.com/emersion/go-smtp v0.24.0 h1:g6AfoF140mvW0vLNPD/LuCBLEAdlxOjIXqbIkJIS6Wk=
|
||||||
|
github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,12 @@ import (
|
||||||
|
|
||||||
"git.happydns.org/happyDeliver/internal/api"
|
"git.happydns.org/happyDeliver/internal/api"
|
||||||
"git.happydns.org/happyDeliver/internal/config"
|
"git.happydns.org/happyDeliver/internal/config"
|
||||||
|
"git.happydns.org/happyDeliver/internal/lmtp"
|
||||||
"git.happydns.org/happyDeliver/internal/storage"
|
"git.happydns.org/happyDeliver/internal/storage"
|
||||||
"git.happydns.org/happyDeliver/web"
|
"git.happydns.org/happyDeliver/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunServer starts the API server server
|
// RunServer starts the API server and LMTP server
|
||||||
func RunServer(cfg *config.Config) error {
|
func RunServer(cfg *config.Config) error {
|
||||||
if err := cfg.Validate(); err != nil {
|
if err := cfg.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -48,6 +49,13 @@ func RunServer(cfg *config.Config) error {
|
||||||
|
|
||||||
log.Printf("Connected to %s database", cfg.Database.Type)
|
log.Printf("Connected to %s database", cfg.Database.Type)
|
||||||
|
|
||||||
|
// Start LMTP server in background
|
||||||
|
go func() {
|
||||||
|
if err := lmtp.StartServer(cfg.Email.LMTPAddr, store, cfg); err != nil {
|
||||||
|
log.Fatalf("Failed to start LMTP server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Create API handler
|
// Create API handler
|
||||||
handler := api.NewAPIHandler(store, cfg)
|
handler := api.NewAPIHandler(store, cfg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ func declareFlags(o *Config) {
|
||||||
flag.StringVar(&o.Database.DSN, "database-dsn", o.Database.DSN, "Database DSN or path")
|
flag.StringVar(&o.Database.DSN, "database-dsn", o.Database.DSN, "Database DSN or path")
|
||||||
flag.StringVar(&o.Email.Domain, "domain", o.Email.Domain, "Domain used to receive emails")
|
flag.StringVar(&o.Email.Domain, "domain", o.Email.Domain, "Domain used to receive emails")
|
||||||
flag.StringVar(&o.Email.TestAddressPrefix, "address-prefix", o.Email.TestAddressPrefix, "Expected email adress prefix (deny address that doesn't start with this prefix)")
|
flag.StringVar(&o.Email.TestAddressPrefix, "address-prefix", o.Email.TestAddressPrefix, "Expected email adress prefix (deny address that doesn't start with this prefix)")
|
||||||
|
flag.StringVar(&o.Email.LMTPAddr, "lmtp-addr", o.Email.LMTPAddr, "LMTP server listen address")
|
||||||
flag.DurationVar(&o.Analysis.DNSTimeout, "dns-timeout", o.Analysis.DNSTimeout, "Timeout when performing DNS query")
|
flag.DurationVar(&o.Analysis.DNSTimeout, "dns-timeout", o.Analysis.DNSTimeout, "Timeout when performing DNS query")
|
||||||
flag.DurationVar(&o.Analysis.HTTPTimeout, "http-timeout", o.Analysis.HTTPTimeout, "Timeout when performing HTTP query")
|
flag.DurationVar(&o.Analysis.HTTPTimeout, "http-timeout", o.Analysis.HTTPTimeout, "Timeout when performing HTTP query")
|
||||||
flag.Var(&StringArray{&o.Analysis.RBLs}, "rbl", "Append a RBL (use this option multiple time to append multiple RBLs)")
|
flag.Var(&StringArray{&o.Analysis.RBLs}, "rbl", "Append a RBL (use this option multiple time to append multiple RBLs)")
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ type DatabaseConfig struct {
|
||||||
type EmailConfig struct {
|
type EmailConfig struct {
|
||||||
Domain string
|
Domain string
|
||||||
TestAddressPrefix string
|
TestAddressPrefix string
|
||||||
|
LMTPAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnalysisConfig contains timeout and behavior settings for email analysis
|
// AnalysisConfig contains timeout and behavior settings for email analysis
|
||||||
|
|
@ -73,6 +74,7 @@ func DefaultConfig() *Config {
|
||||||
Email: EmailConfig{
|
Email: EmailConfig{
|
||||||
Domain: "happydeliver.local",
|
Domain: "happydeliver.local",
|
||||||
TestAddressPrefix: "test-",
|
TestAddressPrefix: "test-",
|
||||||
|
LMTPAddr: "127.0.0.1:2525",
|
||||||
},
|
},
|
||||||
Analysis: AnalysisConfig{
|
Analysis: AnalysisConfig{
|
||||||
DNSTimeout: 5 * time.Second,
|
DNSTimeout: 5 * time.Second,
|
||||||
|
|
|
||||||
144
internal/lmtp/server.go
Normal file
144
internal/lmtp/server.go
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
// This file is part of the happyDeliver (R) project.
|
||||||
|
// Copyright (c) 2025 happyDomain
|
||||||
|
// Authors: Pierre-Olivier Mercier, et al.
|
||||||
|
//
|
||||||
|
// This program is offered under a commercial and under the AGPL license.
|
||||||
|
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||||
|
//
|
||||||
|
// For AGPL licensing:
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package lmtp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
|
|
||||||
|
"git.happydns.org/happyDeliver/internal/config"
|
||||||
|
"git.happydns.org/happyDeliver/internal/receiver"
|
||||||
|
"git.happydns.org/happyDeliver/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Backend implements smtp.Backend for LMTP server
|
||||||
|
type Backend struct {
|
||||||
|
receiver *receiver.EmailReceiver
|
||||||
|
config *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackend creates a new LMTP backend
|
||||||
|
func NewBackend(store storage.Storage, cfg *config.Config) *Backend {
|
||||||
|
return &Backend{
|
||||||
|
receiver: receiver.NewEmailReceiver(store, cfg),
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSession creates a new SMTP/LMTP session
|
||||||
|
func (b *Backend) NewSession(_ *smtp.Conn) (smtp.Session, error) {
|
||||||
|
return &Session{backend: b}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session implements smtp.Session for handling LMTP connections
|
||||||
|
type Session struct {
|
||||||
|
backend *Backend
|
||||||
|
from string
|
||||||
|
recipients []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthPlain implements PLAIN authentication (not used for local LMTP)
|
||||||
|
func (s *Session) AuthPlain(username, password string) error {
|
||||||
|
// No authentication required for local LMTP
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mail is called when MAIL FROM command is received
|
||||||
|
func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
|
||||||
|
log.Printf("LMTP: MAIL FROM: %s", from)
|
||||||
|
s.from = from
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rcpt is called when RCPT TO command is received
|
||||||
|
func (s *Session) Rcpt(to string, opts *smtp.RcptOptions) error {
|
||||||
|
log.Printf("LMTP: RCPT TO: %s", to)
|
||||||
|
s.recipients = append(s.recipients, to)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data is called when DATA command is received and email content is being transferred
|
||||||
|
func (s *Session) Data(r io.Reader) error {
|
||||||
|
log.Printf("LMTP: Receiving message data for %d recipient(s)", len(s.recipients))
|
||||||
|
|
||||||
|
// Read the entire email
|
||||||
|
emailData, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read email data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("LMTP: Received %d bytes", len(emailData))
|
||||||
|
|
||||||
|
// Process email for each recipient
|
||||||
|
// LMTP requires per-recipient status, but go-smtp handles this internally
|
||||||
|
for _, recipient := range s.recipients {
|
||||||
|
if err := s.backend.receiver.ProcessEmailBytes(emailData, recipient); err != nil {
|
||||||
|
log.Printf("LMTP: Failed to process email for %s: %v", recipient, err)
|
||||||
|
return fmt.Errorf("failed to process email for %s: %w", recipient, err)
|
||||||
|
}
|
||||||
|
log.Printf("LMTP: Successfully processed email for %s", recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset is called when RSET command is received
|
||||||
|
func (s *Session) Reset() {
|
||||||
|
log.Printf("LMTP: Session reset")
|
||||||
|
s.from = ""
|
||||||
|
s.recipients = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout is called when the session is closed
|
||||||
|
func (s *Session) Logout() error {
|
||||||
|
log.Printf("LMTP: Session logout")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartServer starts an LMTP server on the specified address
|
||||||
|
func StartServer(addr string, store storage.Storage, cfg *config.Config) error {
|
||||||
|
backend := NewBackend(store, cfg)
|
||||||
|
|
||||||
|
server := smtp.NewServer(backend)
|
||||||
|
server.Addr = addr
|
||||||
|
server.Domain = cfg.Email.Domain
|
||||||
|
server.AllowInsecureAuth = true
|
||||||
|
server.LMTP = true // Enable LMTP mode
|
||||||
|
|
||||||
|
log.Printf("Starting LMTP server on %s", addr)
|
||||||
|
|
||||||
|
// Create TCP listener explicitly
|
||||||
|
listener, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create LMTP listener: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := server.Serve(listener); err != nil {
|
||||||
|
return fmt.Errorf("LMTP server error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue