summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--commands/compose/send-message.go120
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--widgets/compose.go60
4 files changed, 160 insertions, 24 deletions
diff --git a/commands/compose/send-message.go b/commands/compose/send-message.go
index b9fc9d2..b101e12 100644
--- a/commands/compose/send-message.go
+++ b/commands/compose/send-message.go
@@ -1,8 +1,15 @@
 package compose
 
 import (
+	"crypto/tls"
 	"errors"
-	"os"
+	"fmt"
+	"net/mail"
+	"net/url"
+	"strings"
+
+	"github.com/emersion/go-sasl"
+	"github.com/emersion/go-smtp"
 
 	"git.sr.ht/~sircmpwn/aerc2/widgets"
 )
@@ -16,14 +23,115 @@ func SendMessage(aerc *widgets.Aerc, args []string) error {
 		return errors.New("Usage: send-message")
 	}
 	composer, _ := aerc.SelectedTab().(*widgets.Composer)
-	//config := composer.Config()
-	f, err := os.Create("/tmp/test.eml")
+	config := composer.Config()
+
+	if config.Outgoing == "" {
+		return errors.New(
+			"No outgoing mail transport configured for this account")
+	}
+
+	uri, err := url.Parse(config.Outgoing)
 	if err != nil {
-		panic(err)
+		return err
+	}
+	var (
+		scheme string
+		auth   string = "plain"
+	)
+	parts := strings.Split(uri.Scheme, "+")
+	if len(parts) == 1 {
+		scheme = parts[0]
+	} else if len(parts) == 2 {
+		scheme = parts[0]
+		auth = parts[1]
+	} else {
+		return fmt.Errorf("Unknown transfer protocol %s", uri.Scheme)
+	}
+
+	header, rcpts, err := composer.Header()
+	if err != nil {
+		return err
+	}
+
+	if config.From == "" {
+		return errors.New("No 'From' configured for this account")
+	}
+	from, err := mail.ParseAddress(config.From)
+	if err != nil {
+		return err
+	}
+
+	var (
+		saslClient sasl.Client
+		conn       *smtp.Client
+	)
+	switch auth {
+	case "":
+		fallthrough
+	case "none":
+		saslClient = nil
+	case "plain":
+		password, _ := uri.User.Password()
+		saslClient = sasl.NewPlainClient("", uri.User.Username(), password)
+	default:
+		return fmt.Errorf("Unsupported auth mechanism %s", auth)
+	}
+
+	tlsConfig := &tls.Config{
+		// TODO: ask user first
+		InsecureSkipVerify: true,
+	}
+	switch scheme {
+	case "smtp":
+		host := uri.Host
+		if !strings.ContainsRune(host, ':') {
+			host = host + ":587" // Default to submission port
+		}
+		conn, err = smtp.Dial(host)
+		if err != nil {
+			return err
+		}
+		defer conn.Close()
+		if sup, _ := conn.Extension("STARTTLS"); sup {
+			// TODO: let user configure tls?
+			if err = conn.StartTLS(tlsConfig); err != nil {
+				return err
+			}
+		}
+	case "smtps":
+		host := uri.Host
+		if !strings.ContainsRune(host, ':') {
+			host = host + ":465" // Default to smtps port
+		}
+		conn, err = smtp.DialTLS(host, tlsConfig)
+		if err != nil {
+			return err
+		}
+		defer conn.Close()
+	}
+
+	// TODO: sendmail
+	if saslClient != nil {
+		if err = conn.Auth(saslClient); err != nil {
+			return err
+		}
+	}
+	// TODO: the user could conceivably want to use a different From and sender
+	if err = conn.Mail(from.Address); err != nil {
+		return err
+	}
+	for _, rcpt := range rcpts {
+		if err = conn.Rcpt(rcpt); err != nil {
+			return err
+		}
 	}
-	_, err = composer.Message(f)
+	wc, err := conn.Data()
 	if err != nil {
-		panic(err)
+		return err
 	}
+	defer wc.Close()
+	composer.WriteMessage(header, wc)
+	composer.Close()
+	aerc.RemoveTab(composer)
 	return nil
 }
diff --git a/go.mod b/go.mod
index 3d61fa4..a543daf 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,8 @@ require (
 	github.com/emersion/go-imap v1.0.0-beta.4
 	github.com/emersion/go-imap-idle v0.0.0-20180114101550-2af93776db6b
 	github.com/emersion/go-message v0.10.0
+	github.com/emersion/go-sasl v0.0.0-20161116183048-7e096a0a6197
+	github.com/emersion/go-smtp v0.11.0
 	github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect
 	github.com/gdamore/tcell v1.0.0
 	github.com/go-ini/ini v1.42.0
diff --git a/go.sum b/go.sum
index 0134760..c1e0ba0 100644
--- a/go.sum
+++ b/go.sum
@@ -20,6 +20,8 @@ github.com/emersion/go-message v0.10.0 h1:V8hwhZPNIuAIGNLcMZiCzzavUIiODG3COYLsQM
 github.com/emersion/go-message v0.10.0/go.mod h1:7d2eJfhjiJSnlaKcUPq7sEC7ekWELG6F5Lw2BxOGj6Y=
 github.com/emersion/go-sasl v0.0.0-20161116183048-7e096a0a6197 h1:rDJPbyliyym8ZL/Wt71kdolp6yaD4fLIQz638E6JEt0=
 github.com/emersion/go-sasl v0.0.0-20161116183048-7e096a0a6197/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
+github.com/emersion/go-smtp v0.11.0 h1:lM9M2JSxSKEb1dfvB4stkIaIkNJxd5na5Mok8FJDle8=
+github.com/emersion/go-smtp v0.11.0/go.mod h1:CfUbM5NgspbOMHFEgCdoK2PVrKt48HAPtL8hnahwfYg=
 github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
 github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
 github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
diff --git a/widgets/compose.go b/widgets/compose.go
index 318bfc4..38c33fc 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -107,6 +107,19 @@ func (c *Composer) OnInvalidate(fn func(d ui.Drawable)) {
 	})
 }
 
+func (c *Composer) Close() {
+	if c.email != nil {
+		path := c.email.Name()
+		c.email.Close()
+		os.Remove(path)
+		c.email = nil
+	}
+	if c.editor != nil {
+		c.editor.Destroy()
+		c.editor = nil
+	}
+}
+
 func (c *Composer) Event(event tcell.Event) bool {
 	return c.focusable[c.focused].Event(event)
 }
@@ -119,29 +132,19 @@ func (c *Composer) Config() *config.AccountConfig {
 	return c.config
 }
 
-// Writes the email to the given writer, and returns a list of recipients
-func (c *Composer) Message(writeto io.Writer) ([]string, error) {
+func (c *Composer) Header() (*mail.Header, []string, error) {
 	// Extract headers from the email, if present
 	c.email.Seek(0, os.SEEK_SET)
 	var (
 		rcpts  []string
 		header mail.Header
-		body   io.Reader
 	)
 	reader, err := mail.CreateReader(c.email)
 	if err == nil {
 		header = reader.Header
-		// TODO: Do we want to let users write a full blown multipart email
-		// into the editor? If so this needs to change
-		part, err := reader.NextPart()
-		if err != nil {
-			return nil, err
-		}
-		body = part.Body
 		defer reader.Close()
 	} else {
 		c.email.Seek(0, os.SEEK_SET)
-		body = c.email
 	}
 	// Update headers
 	// TODO: Custom header fields
@@ -161,11 +164,11 @@ func (c *Composer) Message(writeto io.Writer) ([]string, error) {
 		// your types aren't compatible enough with each other
 		to_rcpts, err := gomail.ParseAddressList(to)
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		ed_rcpts, err := header.AddressList("To")
 		if err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		for _, addr := range to_rcpts {
 			ed_rcpts = append(ed_rcpts, (*mail.Address)(addr))
@@ -176,14 +179,34 @@ func (c *Composer) Message(writeto io.Writer) ([]string, error) {
 		}
 	}
 	// TODO: Add cc, bcc to rcpts
+	return &header, rcpts, nil
+}
+
+func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
+	c.email.Seek(0, os.SEEK_SET)
+	var body io.Reader
+	reader, err := mail.CreateReader(c.email)
+	if err == nil {
+		// TODO: Do we want to let users write a full blown multipart email
+		// into the editor? If so this needs to change
+		part, err := reader.NextPart()
+		if err != nil {
+			return err
+		}
+		body = part.Body
+		defer reader.Close()
+	} else {
+		c.email.Seek(0, os.SEEK_SET)
+		body = c.email
+	}
 	// TODO: attachments
-	writer, err := mail.CreateSingleInlineWriter(writeto, header)
+	w, err := mail.CreateSingleInlineWriter(writer, *header)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	defer writer.Close()
-	io.Copy(writer, body)
-	return rcpts, nil
+	defer w.Close()
+	_, err = io.Copy(w, body)
+	return err
 }
 
 func (c *Composer) termClosed(err error) {
@@ -191,6 +214,7 @@ func (c *Composer) termClosed(err error) {
 	c.grid.RemoveChild(c.editor)
 	c.grid.AddChild(newReviewMessage(c)).At(1, 0)
 	c.editor.Destroy()
+	c.editor = nil
 }
 
 func (c *Composer) PrevField() {
/subx/016index_addressing.cc?h=hlt&id=4a943d4ed313eff001504c2b5c472266e86a38af'>4a943d4e ^
83c67014 ^
4a943d4e ^



83c67014 ^
4a943d4e ^








d1df4aca ^


4a943d4e ^





83c67014 ^
4a943d4e ^



83c67014 ^
4a943d4e ^









d1df4aca ^







4a943d4e ^





83c67014 ^
4a943d4e ^



83c67014 ^
4a943d4e ^









d1df4aca ^




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155