mhale / smtpd Goto Github PK
View Code? Open in Web Editor NEWAn SMTP server package written in Go, in the style of the built-in HTTP server.
License: The Unlicense
An SMTP server package written in Go, in the style of the built-in HTTP server.
License: The Unlicense
Line 417 in 438c8ed
The Line makes "little sence" to me. Should be
if len(to) > 100 {
As more then 100 can me denied.
I want to support accepting large SMTP DATA
bodies without having to store the body in memory. Using a disk buffer or "memory-constrained buffer" would allow the server to accept larger bodies from multiple clients concurrently.
https://github.com/mhale/smtpd/blob/master/smtpd.go#L603
// Buffer up to ~6MB in RAM and limit max buffer size to ~200MB
multibuf.New(r, multibuf.MemBytes(1024 * 1024 * 6), multibuf.MaxBytes(1024 * 1024 * 200))
https://cipherli.st/ is down, do you have alternative?
How to send DATA end?
https://github.com/mhale/smtpd/blob/master/smtpd.go#L252
$ nc localhost 2525
220 localhost MyServerApp SMTP Service ready
EHLO hello server
250 localhost greets hello server
MAIL FROM:<[email protected]>
250 Ok
RCPT TO:<[email protected]>
250 Ok
DATA
354 Start mail input; end with <CR><LF>.<CR><LF>
From:[email protected]
To:[email protected]
Subject:Test
<h1>Test</h1>
\r\n.\r\n
.\r\n
but nothing.
My locale
LANG=ru_RU.UTF-8
LC_CTYPE=ru_RU.UTF-8
LC_NUMERIC="ru_RU.UTF-8"
LC_TIME="ru_RU.UTF-8"
LC_COLLATE="ru_RU.UTF-8"
LC_MONETARY="ru_RU.UTF-8"
LC_MESSAGES="ru_RU.UTF-8"
LC_PAPER="ru_RU.UTF-8"
LC_NAME="ru_RU.UTF-8"
LC_ADDRESS="ru_RU.UTF-8"
LC_TELEPHONE="ru_RU.UTF-8"
LC_MEASUREMENT="ru_RU.UTF-8"
LC_IDENTIFICATION="ru_RU.UTF-8"
LC_ALL=
We found that two of our systems are unable to send emails to our service that uses smtpd as the smtp handler. Tracking it down to the errors for 'invalid FROM parameter' and 'invalid TO parameter'. Found that updating the regex to allow for a single space handles this and then trimmed the addresses as they were added. I was hoping this could be evaluated and added so that we don't have to use a custom fork. Had issues making a PR so adding the code changes here. Thank you.
26:rcptToRE = regexp.MustCompile(`[Tt][Oo]:\s?<(.+)>`)
27:mailFromRE = regexp.MustCompile(`[Ff][Rr][Oo][Mm]:\s?<(.*)>(\s(.*))?`) // Delivery Status Notifications are sent with "MAIL FROM:<>"
294:from = strings.TrimSpace(match[1])
334:to = append(to, strings.TrimSpace(match[1]))
is there tls support planned or can you provide guidance how to implement it?
Hi there.
Currently there is a maximum of 100 recipients hardcoded. Whilst I understand that this should be a default, the RFC states that an SMTP server must handle a minimum of 100 recipients, not the maximum. I use your awesome package in Mailpit (email / SMTP testing tool) and it would be extremely helpful if your module could allow a custom max to be set.
Thank you.
You mentioned that you modeled it after the built-in http server. It has a Shutdown method for gracefully shutting down the service. How do I do the equivalent with smtpd? Thanks!
I apologise in advance for the non technical question, but i am attempting to set up a "simple" (ha) service to receive incoming mail (*@domain.com) and dump it into an sql database.
This looked ideal, but my lack of understanding exactly how email works has me stumped.
I have built the example used in the readme, but listening on port 25 and using fmt.Printf
so i can see the output in the terminal. I uploaded the binary to a test server, ssh in and ran it sudo ./go-email
.
I can connect with telnet from my local machine telnet 195.201.233.74 25
and it connect fine.
I have added A and MX records on a subdomain (gomail.fl9.uk), and checked with mxtoolbox, and it seems happy:
https://mxtoolbox.com/SuperTool.aspx?action=mx%3agomail.fl9.uk&run=toolpage#
Connecting to 195.201.233.74
220 ubuntu-2gb-fsn1-1 MyServerApp ESMTP Service ready [742 ms]
EHLO keeper-us-east-1c.mxtoolbox.com
250-ubuntu-2gb-fsn1-1 greets keeper-us-east-1c.mxtoolbox.com
250-SIZE 0
250 ENHANCEDSTATUSCODES [696 ms]
MAIL FROM:<[email protected]>
250 2.1.0 Ok [703 ms]
RCPT TO:<[email protected]>
250 2.1.5 Ok [703 ms]
LookupServer 4500ms
I can send emails from my gmail, to [email protected], with no bounces or other errors.
However, i am not getting any output in the terminal thats running the program.
Have i completely misunderstood how this works?
great work! i've managed to get everything set up but dunno how to get the body of the msg. I know how to get the subject but how do i get the body?
msg, _ := mail.ReadMessage(bytes.NewReader(data))
subject := msg.Header.Get("Subject")
log.Printf("Received mail from %s for %s with subject %s", from, to[0], subject)
return nil
Hi,
I tried to use your simple example in readme.md
But calling mail.ReadMessage
actually raises an error malformed MIME header: missing colon: "..."
func mailHandler(origin net.Addr, from string, to []string, data []byte) error {
msg, err := mail.ReadMessage(bytes.NewReader(data))
if err != nil {
log.Print(err)
return nil
}
return nil
}
It seems that the mail
package is not able to parse the RFC 2821 Received header
properly. Any idea how to fix this?
Hello! Thanks for creating this library, it's really useful and I love the simplicity π I'm looking at the example for authentication that you give in the readme...
func authHandler(remoteAddr net.Addr, mechanism string, username []byte, password []byte, shared []byte) (bool, error) {
return string(username) == "valid" && string(password) == "password", nil
}
Is there a way to store some kind of state at authentication time that can later be used in a mail handler? That way you could, for instance, only allow sending emails that are from that user's account.
If not, do you have any ideas for how to implement this? I'd be really happy to help with an implementation π
(as a side-note, docs for this library can't be viewed at pkg.go.dev/github.com/mhale/smtpd because you don't have a license, you may want to create a simple LICENSE file)
For the moment, if I'm not wrong, I have no way to answer a different SMTP response according to my code.
For example I want to implement a rate-limiting and so want to answer SMTP Error 451
if exceeded.
Nice job for the implementation! I was wondering, how about this implementation could be versioned and published so it could be used as a library? I'm using the GitHub link right now for importing, but the version is 0.0.0 and could be prone to new changes if any.
The version I retrieve from my go.mod
file with the unclear versioning is: v0.0.0-20210322105601-438c8edb069c
.
It seems like if a client connects, it can stay connected forever - would be good to have a timeout on idle connections. π
could you please write very basic example of MTA?
Hello! I really want to use this package in my project, but there is one major problem: it's impossible to use this package in go.mod file in my project because of package and module different names ("github.com/mhale/smtpd" and "github.com/prologic/smtpd" respectively), so go.mod can't find module to use. Would be awesome if you fixed this!
Please add support for authentication. It will be very useful to have a callback to check the username and password.
What happens if the length is bigger than 100?
Should this not be >= 100?
Line 326 in d7a07f7
how i can use it for send email for other server like @gmail.com
The net.LookupAddr
function is used at a few places in the code, for example right after accepting a connection. If that function does not terminate quickly big response times could arise that risk timeouts or major slowdowns.
I suggest to remove the lookup because it seems this is only used for diagnostics and the reverse lookup is flaky anyway. Please be aware that even if instead a very short timeout is added this can easily pile up to a major slowdown if called hundreds of times, say in a CI test. Even when the lookup is working as intended this can be a relatively slow process that may involve several DNS lookups.
I am using this trough axllent/mailpit for local development and CI. In my Gitlab CI setup several services are running that try to connect via SMTP. For some reason in my particular environment net.LookupAddr
takes 10s (returns an error). Unfortunately that does not only slow down things a lot but also breaks several timeouts.
Arguably there is something off with the reverse lookup in my environment but because the lookup is such an unreliable thing to do I would use that opportunity to get rid of it π’ In case you are interested, I think it is a bug somewhere in Docker, musl or Go (what a line upβ¦), but it is hard to get a small reproducible setup of this, currently I can reproduce that only through jobs in a Gitlab runnerβ¦
It was a quite a journey to spot this issue, but I was able to reproduce the 10s spent between the initial TCP SYN and the first message (220 host Mailpit ESMTP Service ready
) by isolating the net.LookupAddr
call that was made, and all of that in CI jobs, what a day π. Hope that saves someone else's day.
Currently after the DATA command is issued the response always returns as 250 2.0.0 Ok: queued
, some systems rely on receiving the SMTP Message ID in the SMTP response for logging purposes.
It would be good if either a custom function could be passed in for generating the SMTP Message ID, or if the Handle function could return a Message ID that will be used in the response.
Ideally the response would be formatted as 250 2.0.0 Ok: queued as <message-id>
I don't think this comparison check is correct. I'm sure this was supposed to be len(to) > 100
with a different comment:
Line 326 in 3c4c908
Hello, I'm looking at implementing a graceful shutdown but can't quite get it to work. Here's my code that starts the SMTP listener:
func listenEmail(ctx context.Context, port string) {
srv := &smtpd.Server{
Addr: "0.0.0.0:" + port,
Handler: mailHandler,
Appname: "...",
Hostname: "...",
}
go func() {
<- ctx.Done()
log.Println("We should stop the SMTP server now...")
srv.Close()
log.Println("We closed it")
}()
err := srv.ListenAndServe()
if err != nil {
log.Fatal("Unable to start smtp server: ", err)
os.Exit(1)
}
log.Println("listenEmail goroutine quits")
}
Not shown here, but on Ctrl+C, ctx
gets cancelled, so the inner goroutine runs and calls srv.Close()
. When I run this, the "We closed it" message from the inner goroutine gets printed, but the final "listenEmail goroutine quits" does not. I assume that's because ListenAndServe
still keeps blocking.
I was looking at the smtpd source code:
// Serve creates a new SMTP session after a network connection is established.
func (srv *Server) Serve(ln net.Listener) error {
if atomic.LoadInt32(&srv.inShutdown) != 0 {
return ErrServerClosed
}
defer ln.Close()
for {
// if we are shutting down, don't accept new connections
select {
case <-srv.getShutdownChan():
return ErrServerClosed
default:
}
conn, err := ln.Accept()
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
continue
}
return err
}
session := srv.newSession(conn)
atomic.AddInt32(&srv.openSessions, 1)
go session.serve()
}
}
To me it looks like it checks the shutdown condition at the start of the for loop, and then blocks at ln.Accept()
. So, I'm guessing, it needs to receive one extra email for the loop to advance and the Serve
call to return. Does that make sense? I'm a Go novice and am stepping on rakes and making wrong guesses left and right :-)
I also run a HTTP listener (http.Server) in a similar way, and it is able to finish up immediately, without waiting for one extra HTTP request. Perhaps there's a viable way to make smtpd work the same?
Originally posted by @cuu508 in #27 (comment)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.