Skip to content

Commit bbbb563

Browse files
committed
write recipient
1 parent f6dfbde commit bbbb563

File tree

11 files changed

+420
-36
lines changed

11 files changed

+420
-36
lines changed

README.md

+20-11
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,28 @@ PostmanQ разбирает одну или несколько очередей
6464
openssl x509 -req -in request.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out example.crt -days 5000
6565
# создаем публичный ключ из приватного
6666
openssl rsa -in private.key -pubout > public.key
67-
68-
Далее добавляем DKIM и SPF записи в DNS:
69-
70-
_domainkey.example.com. TXT "t=s; o=~;"
67+
68+
Теперь необходимо настроить DNS.
69+
70+
PostmanQ должен представляться в команде HELO/EHLO своим полным доменным именем(FQDN) почты.
71+
72+
FQDN почты должно быть указано в A записи с внешним IP.
73+
74+
PTR запись должна указывать на FQDN почты.
75+
76+
MX запись должна указывать на FQDN почты.
77+
78+
Также необходимо указать DKIM и SPF записи.
79+
80+
example.com. A 1.1.1.1
81+
example.com. MX 10 mail.example.com.
82+
mail.example.com. A 1.2.3.4
83+
4.3.2.1.in-addr.arpa. IN PTR mail.example.com.
84+
_domainkey.example.com. TXT "t=s; o=~;"
7185
selector._domainkey.example.com. 3600 IN TXT "k=rsa\; t=s\; p=содержимое public.key"
72-
example.com. IN TXT "v=spf1 +a +mx ~all"
73-
86+
example.com. IN TXT "v=spf1 +a +mx ~all"
87+
7488
Selector-ом может быть любым словом на латинице. Значение selector-а необходимо указать в настройках PostmanQ в поле dkimSelector.
75-
76-
Кроме того необходимо прописать PTR-запись, которой соответствовала бы А-запись, с которой идет рассылка.
77-
78-
# PTR запись для 1.2.3.4:
79-
4.3.2.1.in-addr.arpa IN PTR mail.example.com
8089

8190
Если PTR запись отсутствует, то письма могут попадать в спам, либо почтовые сервисы могут отклонять отправку.
8291

application/post.go

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/actionpay/postmanq/limiter"
99
"github.com/actionpay/postmanq/logger"
1010
"github.com/actionpay/postmanq/mailer"
11+
"github.com/actionpay/postmanq/recipient"
1112
yaml "gopkg.in/yaml.v2"
1213
"runtime"
1314
)
@@ -41,6 +42,7 @@ func (p *Post) Run() {
4142
limiter.Inst(),
4243
connector.Inst(),
4344
mailer.Inst(),
45+
recipient.Inst(),
4446
}
4547
p.run(p, common.NewApplicationEvent(common.InitApplicationEventKind))
4648
}

cmd/push_mails.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ func main() {
1111

1212
// amqpURI := "amqp://admin:[email protected]:5672/postmanq"
1313
amqpURI := "amqp://solomonov:123123123@localhost:5672/postmanq"
14-
messageCount := 10
14+
messageCount := 1
1515
// hasError := true
1616
hasError := false
1717
exchange := "postmanq"
1818
// exchange := "postmanq.failure.recipient"
1919
envelope := "[email protected]"
20-
recipient := "[email protected]"
21-
// recipient := "[email protected]"
20+
recipient := "[email protected]"
21+
//recipient := "[email protected]"
2222
// recipient := "[email protected]""
2323
// recipient := "[email protected]"
2424
// recipient := "[email protected]"

common/post.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const (
1717

1818
var (
1919
// Регулярка для проверки адреса почты, сразу компилируем, чтобы при отправке не терять на этом время
20-
EmailRegexp = regexp.MustCompile(`^[\w\d\.\_\%\+\-]+@([\w\d\.\-]+\.\w{2,4})$`)
20+
EmailRegexp = regexp.MustCompile(`^[\w\d\.\_\%\+\-]+@([\w\d\.\-]+\.\w{2,5})$`)
2121
EmptyStrSlice = []string{}
2222
)
2323

config.yaml

+5-1
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,8 @@ postmans:
108108
type: day
109109

110110
# максимальное количество писем, которое может быть отправлено за период
111-
value: 150
111+
value: 150
112+
113+
listenerCount: 20
114+
115+
inbox: example

connector/connector.go

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package connector
22

33
import (
4-
"crypto/tls"
54
"errors"
65
"fmt"
76
"github.com/actionpay/postmanq/common"
@@ -155,13 +154,10 @@ func (c *Connector) createSmtpClient(mxServer *MxServer, event *ConnectionEvent,
155154
// открывает защищенное соединение
156155
func (c *Connector) initTlsSmtpClient(mxServer *MxServer, event *ConnectionEvent, ptrSmtpClient **common.SmtpClient, connection net.Conn, client *smtp.Client) {
157156
// если есть какие данные о сертификате и к серверу можно создать TLS соединение
158-
pool := service.getPool(event.Message.HostnameFrom)
159-
if pool != nil && mxServer.useTLS {
157+
conf := service.getTlsConfig(event.Message.HostnameFrom)
158+
if conf != nil && mxServer.useTLS {
160159
// открываем TLS соединение
161-
err := client.StartTLS(&tls.Config{
162-
ClientCAs: pool,
163-
ServerName: mxServer.realServerName,
164-
})
160+
err := client.StartTLS(conf)
165161
// если все нормально, создаем клиента
166162
if err == nil {
167163
c.initSmtpClient(mxServer, event, ptrSmtpClient, connection, client)

connector/service.go

+43-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package connector
22

33
import (
4+
"crypto/tls"
45
"crypto/x509"
56
"encoding/pem"
67
"github.com/actionpay/postmanq/common"
@@ -20,6 +21,17 @@ var (
2021

2122
// почтовые сервисы будут хранится в карте по домену
2223
mailServers = make(map[string]*MailServer)
24+
25+
cipherSuites = []uint16{
26+
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
27+
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
28+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
29+
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
30+
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
31+
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
32+
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
33+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
34+
}
2335
)
2436

2537
// сервис, управляющий соединениями к почтовым сервисам
@@ -60,17 +72,34 @@ func (s *Service) OnInit(event *common.ApplicationEvent) {
6072
func (s *Service) init(conf *Config, hostname string) {
6173
// если указан путь до сертификата
6274
if len(conf.CertFilename) > 0 {
75+
conf.tlsConfig = &tls.Config{
76+
ClientAuth: tls.RequireAndVerifyClientCert,
77+
CipherSuites: cipherSuites,
78+
MinVersion: tls.VersionTLS12,
79+
SessionTicketsDisabled: true,
80+
}
81+
6382
// пытаемся прочитать сертификат
6483
pemBytes, err := ioutil.ReadFile(conf.CertFilename)
6584
if err == nil {
6685
// получаем сертификат
6786
pemBlock, _ := pem.Decode(pemBytes)
6887
cert, _ := x509.ParseCertificate(pemBlock.Bytes)
69-
conf.pool = x509.NewCertPool()
70-
conf.pool.AddCert(cert)
88+
pool := x509.NewCertPool()
89+
pool.AddCert(cert)
90+
conf.tlsConfig.RootCAs = pool
91+
conf.tlsConfig.ClientCAs = pool
7192
} else {
7293
logger.By(hostname).FailExit("connection service can't read certificate %s, error - %v", conf.CertFilename, err)
7394
}
95+
cert, err := tls.LoadX509KeyPair(conf.CertFilename, conf.PrivateKeyFilename)
96+
if err == nil {
97+
conf.tlsConfig.Certificates = []tls.Certificate{
98+
cert,
99+
}
100+
} else {
101+
logger.By(hostname).FailExit("connection service can't load certificate %s, private key %s, error - %v", conf.CertFilename, conf.PrivateKeyFilename, err)
102+
}
74103
} else {
75104
logger.By(hostname).Debug("connection service - certificate is not defined")
76105
}
@@ -106,11 +135,19 @@ func (s *Service) OnFinish() {
106135
close(events)
107136
}
108137

109-
func (s Service) getPool(hostname string) *x509.CertPool {
138+
func (s Service) getTlsConfig(hostname string) *tls.Config {
110139
if conf, ok := s.Configs[hostname]; ok {
111-
return conf.pool
140+
//tlsConfig := new(tls.Config)
141+
//tlsConfig.Certificates = conf.certs
142+
//tlsConfig.RootCAs = conf.pool
143+
//tlsConfig.ClientCAs = conf.pool
144+
//tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
145+
//tlsConfig.CipherSuites = cipherSuites
146+
//tlsConfig.MinVersion = tls.VersionTLS12
147+
//tlsConfig.SessionTicketsDisabled = true
148+
return conf.tlsConfig
112149
} else {
113-
logger.By(hostname).Err("connection service can't find cert by %s", hostname)
150+
logger.By(hostname).Err("connection service can't make tls config by %s", hostname)
114151
return nil
115152
}
116153
}
@@ -172,8 +209,7 @@ type Config struct {
172209
// количество ip
173210
addressesLen int
174211

175-
// пул сертификатов
176-
pool *x509.CertPool
212+
tlsConfig *tls.Config
177213

178214
hostname string
179215
}

install.sh

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
#!/bin/bash
2-
BASE_PATH=`pwd`
3-
VERSION="v.3.1"
42

53
if [[ "$(whoami)" != "root" ]]
64
then
@@ -10,16 +8,18 @@ fi
108

119
if [[ -z "$(which git)" ]]
1210
then
13-
echo "sorry, git are not installed"
14-
exit 1
11+
echo "git are not installed!"
12+
exit 2
1513
fi
1614

1715
if [[ -z "$(which go)" ]]
1816
then
19-
echo "sorry, go are not installed"
20-
exit 1
17+
echo "go are not installed!"
18+
exit 3
2119
fi
2220

21+
BASE_PATH=`pwd`
22+
VERSION="v.3.1"
2323
export GOPATH="$BASE_PATH"
2424
export GOBIN="$BASE_PATH/bin/"
2525
go get -d github.com/actionpay/postmanq/cmd

recipient/recipient.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package recipient
2+
3+
import (
4+
"cmd/go/testdata/src/vend/x/vendor/r"
5+
"net"
6+
"net/textproto"
7+
)
8+
9+
//type RecipientState int
10+
//
11+
//const (
12+
// Ehlo RecipientState = iota
13+
// Helo
14+
// Mail
15+
// Rcpt
16+
// Data
17+
// Rset
18+
// Noop
19+
// Vrfy
20+
// Quit
21+
//)
22+
//
23+
//var (
24+
// nexts = map[RecipientState][]RecipientState {
25+
// Ehlo: []RecipientState{Mail, Rset, Noop, Quit, Vrfy},
26+
// Helo: []RecipientState{Mail, Rset, Noop, Quit, Vrfy},
27+
// Mail: []RecipientState{Rcpt, Rset, Noop, Quit},
28+
// Rcpt: []RecipientState{Data, Rcpt, Rset, Noop, Quit},
29+
// Data: []RecipientState{Mail, Rset, Noop, Quit},
30+
// Rset: []RecipientState{Mail, Rset, Noop, Quit},
31+
// Noop: []RecipientState{},
32+
// Vrfy: []RecipientState{},
33+
// Quit: []RecipientState{},
34+
// }
35+
//)
36+
//
37+
//func (r RecipientState) getName() []byte {}
38+
39+
type Recipient struct {
40+
id int
41+
state State
42+
conn net.Conn
43+
txt *textproto.Conn
44+
}
45+
46+
func newRecipient(id int, events chan *Event) {
47+
mail := new(MailState)
48+
mail.SetPossibles([]State{})
49+
50+
ehlo := new(EhloState)
51+
ehlo.SetNext(mail)
52+
ehlo.SetPossibles([]State{})
53+
54+
conn := new(ConnectState)
55+
conn.SetNext(ehlo)
56+
conn.SetPossibles([]State{})
57+
58+
recipient := &Recipient{
59+
id: id,
60+
state: conn,
61+
}
62+
for event := range events {
63+
recipient.handle(event)
64+
}
65+
}
66+
67+
func (r *Recipient) handle(event *Event) {
68+
r.txt = textproto.NewConn(event.conn)
69+
70+
for {
71+
r.state.SetEvent(event)
72+
status := r.state.Read(r.txt)
73+
goto handleStatus
74+
75+
handleStatus:
76+
switch status {
77+
case SuccessStatus:
78+
r.state.Write(r.txt)
79+
r.state = r.state.GetNext()
80+
81+
case FailureStatus:
82+
r.state.Write(r.txt)
83+
84+
case PossibleStatus:
85+
var possibleStatus StateStatus
86+
for _, possible := range r.state.GetPossibles() {
87+
possible.SetEvent(event)
88+
possibleStatus = possible.Read(r.txt)
89+
if possibleStatus == SuccessStatus {
90+
r.state = possible
91+
status = possibleStatus
92+
goto handleStatus
93+
}
94+
}
95+
r.txt.Cmd("500 Syntax error, command unrecognized")
96+
97+
}
98+
return
99+
}
100+
}

0 commit comments

Comments
 (0)