Makeing some progress...

This commit is contained in:
Winni Neessen 2022-03-05 16:27:09 +01:00
parent 4dd9c1bf40
commit 261481344f
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
5 changed files with 190 additions and 18 deletions

9
.idea/markdown.xml Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<enabledExtensions>
<entry key="MermaidLanguageExtension" value="false" />
<entry key="PlantUMLLanguageExtension" value="false" />
</enabledExtensions>
</component>
</project>

147
client.go
View file

@ -2,10 +2,16 @@ package mail
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/smtp"
"os"
"time"
)
// DefaultPort is the default connection port to the SMTP server
// DefaultPort is the default connection port cto the SMTP server
const DefaultPort = 25
// DefaultTimeout is the default connection timeout
@ -13,19 +19,50 @@ const DefaultTimeout = time.Second * 30
// Client is the SMTP client struct
type Client struct {
h string // Hostname of the target SMTP server to connect to
p int // Port of the SMTP server to connect to
s bool // Use SSL/TLS or not
ctx context.Context // The context for the connection handling
// Hostname of the target SMTP server cto connect cto
host string
// Port of the SMTP server cto connect cto
port int
// Use SSL for the connection
ssl bool
// Sets the client cto use STARTTTLS for the connection (is disabled when SSL is set)
starttls bool
// Timeout for the SMTP server connection
cto time.Duration
// HELO/EHLO string for the greeting the target SMTP server
helo string
// The SMTP client that is set up when using the Dial*() methods
sc *smtp.Client
}
// Option returns a function that can be used for grouping Client options
type Option func(*Client)
var (
// ErrNoHostname should be used if a Client has no hostname set
ErrNoHostname = errors.New("hostname for client cannot be empty")
// ErrInvalidHostname should be used if a Client has an invalid hostname set
//ErrInvalidHostname = errors.New("hostname for client is invalid")
)
// NewClient returns a new Session client object
func NewClient(o ...Option) Client {
c := Client{
p: DefaultPort,
func NewClient(h string, o ...Option) (*Client, error) {
c := &Client{
host: h,
port: DefaultPort,
cto: DefaultTimeout,
}
// Set default HELO/EHLO hostname
if err := c.setDefaultHelo(); err != nil {
return c, err
}
// Override defaults with optionally provided Option functions
@ -33,26 +70,100 @@ func NewClient(o ...Option) Client {
if co == nil {
continue
}
co(&c)
co(c)
}
return c
}
// Some settings in a Client cannot be empty/unset
if c.host == "" {
return c, ErrNoHostname
// WithHost overrides the default connection port
func WithHost(h string) Option {
return func(c *Client) {
c.h = h
}
return c, nil
}
// WithPort overrides the default connection port
func WithPort(p int) Option {
return func(c *Client) {
c.p = p
c.port = p
}
}
func (c Client) Dial() {
// WithTimeout overrides the default connection timeout
func WithTimeout(t time.Duration) Option {
return func(c *Client) {
c.cto = t
}
}
// WithSSL tells the client to use a SSL/TLS connection
func WithSSL() Option {
return func(c *Client) {
c.ssl = true
}
}
// Dial establishes a connection cto the SMTP server with a default context.Background
func (c *Client) Dial() error {
ctx := context.Background()
return c.DialWithContext(ctx)
}
// DialWithContext establishes a connection cto the SMTP server with a given context.Context
func (c *Client) DialWithContext(uctx context.Context) error {
ctx, cfn := context.WithTimeout(uctx, c.cto)
defer cfn()
nd := net.Dialer{}
td := tls.Dialer{}
var co net.Conn
var err error
if c.ssl {
co, err = td.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", c.host, c.port))
}
if !c.ssl {
co, err = nd.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", c.host, c.port))
}
if err != nil {
return err
}
c.sc, err = smtp.NewClient(co, c.host)
if err != nil {
return err
}
if err := c.sc.Hello(c.helo); err != nil {
return err
}
return nil
}
// Send sends out the mail message
func (c *Client) Send() error {
return nil
}
// Close closes the connection cto the SMTP server
func (c *Client) Close() error {
if err := c.sc.Close(); err != nil {
fmt.Printf("failed close: %s\n", err)
return err
}
if ok, auth := c.sc.Extension("PIPELINING"); ok {
fmt.Printf("PIPELINING Support: %s\n", auth)
} else {
fmt.Println("No PIPELINING")
}
return c.sc.Close()
}
// setDefaultHelo retrieves the current hostname and sets it as HELO/EHLO hostname
func (c *Client) setDefaultHelo() error {
hn, err := os.Hostname()
if err != nil {
return fmt.Errorf("failed cto read local hostname: %w", err)
}
c.helo = hn
return nil
}

31
cmd/main.go Normal file
View file

@ -0,0 +1,31 @@
package main
import (
"context"
"fmt"
"github.com/wneessen/go-mail"
"os"
"time"
)
func main() {
c, err := mail.NewClient("192.168.178.60", mail.WithTimeout(time.Millisecond*500), mail.WithSSL())
if err != nil {
fmt.Printf("failed to create new client: %s\n", err)
os.Exit(1)
}
ctx, cfn := context.WithCancel(context.Background())
defer cfn()
if err := c.DialWithContext(ctx); err != nil {
fmt.Printf("failed to dial: %s\n", err)
os.Exit(1)
}
fmt.Printf("Client: %+v\n", c)
time.Sleep(time.Millisecond * 1500)
if err := c.Close(); err != nil {
fmt.Printf("failed to close SMTP connection: %s\n", err)
os.Exit(1)
}
}

2
doc.go Normal file
View file

@ -0,0 +1,2 @@
// Package mail provides a simple and easy way cto sending mails with Go
package mail

19
tls.go Normal file
View file

@ -0,0 +1,19 @@
package mail
// TLSPolicy type describes a int alias for the different TLS policies we allow
type TLSPolicy int
const (
// TLSMandatory requires that the connection cto the server is
// encrypting using STARTTLS. If the server does not support STARTTLS
// the connection will be terminated with an error
TLSMandatory TLSPolicy = iota
// TLSOpportunistic tries cto establish an encrypted connection via the
// STARTTLS protocol. If the server does not support this, it will fall
// back cto non-encrypted plaintext transmission
TLSOpportunistic
// NoTLS forces the transaction cto be not encrypted
NoTLS
)