Skip to content

Commit

Permalink
write recipient
Browse files Browse the repository at this point in the history
  • Loading branch information
byorty committed Apr 6, 2016
1 parent f6dfbde commit bbbb563
Show file tree
Hide file tree
Showing 11 changed files with 420 additions and 36 deletions.
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,28 @@ PostmanQ разбирает одну или несколько очередей
openssl x509 -req -in request.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out example.crt -days 5000
# создаем публичный ключ из приватного
openssl rsa -in private.key -pubout > public.key
Далее добавляем DKIM и SPF записи в DNS:

_domainkey.example.com. TXT "t=s; o=~;"
Теперь необходимо настроить DNS.

PostmanQ должен представляться в команде HELO/EHLO своим полным доменным именем(FQDN) почты.

FQDN почты должно быть указано в A записи с внешним IP.

PTR запись должна указывать на FQDN почты.

MX запись должна указывать на FQDN почты.

Также необходимо указать DKIM и SPF записи.

example.com. A 1.1.1.1
example.com. MX 10 mail.example.com.
mail.example.com. A 1.2.3.4
4.3.2.1.in-addr.arpa. IN PTR mail.example.com.
_domainkey.example.com. TXT "t=s; o=~;"
selector._domainkey.example.com. 3600 IN TXT "k=rsa\; t=s\; p=содержимое public.key"
example.com. IN TXT "v=spf1 +a +mx ~all"

example.com. IN TXT "v=spf1 +a +mx ~all"
Selector-ом может быть любым словом на латинице. Значение selector-а необходимо указать в настройках PostmanQ в поле dkimSelector.

Кроме того необходимо прописать PTR-запись, которой соответствовала бы А-запись, с которой идет рассылка.

# PTR запись для 1.2.3.4:
4.3.2.1.in-addr.arpa IN PTR mail.example.com

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

Expand Down
2 changes: 2 additions & 0 deletions application/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/actionpay/postmanq/limiter"
"github.com/actionpay/postmanq/logger"
"github.com/actionpay/postmanq/mailer"
"github.com/actionpay/postmanq/recipient"
yaml "gopkg.in/yaml.v2"
"runtime"
)
Expand Down Expand Up @@ -41,6 +42,7 @@ func (p *Post) Run() {
limiter.Inst(),
connector.Inst(),
mailer.Inst(),
recipient.Inst(),
}
p.run(p, common.NewApplicationEvent(common.InitApplicationEventKind))
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/push_mails.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ func main() {

// amqpURI := "amqp://admin:[email protected]:5672/postmanq"
amqpURI := "amqp://solomonov:123123123@localhost:5672/postmanq"
messageCount := 10
messageCount := 1
// hasError := true
hasError := false
exchange := "postmanq"
// exchange := "postmanq.failure.recipient"
envelope := "[email protected]"
recipient := "[email protected]"
// recipient := "[email protected]"
recipient := "[email protected]"
//recipient := "[email protected]"
// recipient := "[email protected]""
// recipient := "[email protected]"
// recipient := "[email protected]"
Expand Down
2 changes: 1 addition & 1 deletion common/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const (

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

Expand Down
6 changes: 5 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,8 @@ postmans:
type: day

# максимальное количество писем, которое может быть отправлено за период
value: 150
value: 150

listenerCount: 20

inbox: example
10 changes: 3 additions & 7 deletions connector/connector.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package connector

import (
"crypto/tls"
"errors"
"fmt"
"github.com/actionpay/postmanq/common"
Expand Down Expand Up @@ -155,13 +154,10 @@ func (c *Connector) createSmtpClient(mxServer *MxServer, event *ConnectionEvent,
// открывает защищенное соединение
func (c *Connector) initTlsSmtpClient(mxServer *MxServer, event *ConnectionEvent, ptrSmtpClient **common.SmtpClient, connection net.Conn, client *smtp.Client) {
// если есть какие данные о сертификате и к серверу можно создать TLS соединение
pool := service.getPool(event.Message.HostnameFrom)
if pool != nil && mxServer.useTLS {
conf := service.getTlsConfig(event.Message.HostnameFrom)
if conf != nil && mxServer.useTLS {
// открываем TLS соединение
err := client.StartTLS(&tls.Config{
ClientCAs: pool,
ServerName: mxServer.realServerName,
})
err := client.StartTLS(conf)
// если все нормально, создаем клиента
if err == nil {
c.initSmtpClient(mxServer, event, ptrSmtpClient, connection, client)
Expand Down
50 changes: 43 additions & 7 deletions connector/service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package connector

import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"github.com/actionpay/postmanq/common"
Expand All @@ -20,6 +21,17 @@ var (

// почтовые сервисы будут хранится в карте по домену
mailServers = make(map[string]*MailServer)

cipherSuites = []uint16{
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
}
)

// сервис, управляющий соединениями к почтовым сервисам
Expand Down Expand Up @@ -60,17 +72,34 @@ func (s *Service) OnInit(event *common.ApplicationEvent) {
func (s *Service) init(conf *Config, hostname string) {
// если указан путь до сертификата
if len(conf.CertFilename) > 0 {
conf.tlsConfig = &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
CipherSuites: cipherSuites,
MinVersion: tls.VersionTLS12,
SessionTicketsDisabled: true,
}

// пытаемся прочитать сертификат
pemBytes, err := ioutil.ReadFile(conf.CertFilename)
if err == nil {
// получаем сертификат
pemBlock, _ := pem.Decode(pemBytes)
cert, _ := x509.ParseCertificate(pemBlock.Bytes)
conf.pool = x509.NewCertPool()
conf.pool.AddCert(cert)
pool := x509.NewCertPool()
pool.AddCert(cert)
conf.tlsConfig.RootCAs = pool
conf.tlsConfig.ClientCAs = pool
} else {
logger.By(hostname).FailExit("connection service can't read certificate %s, error - %v", conf.CertFilename, err)
}
cert, err := tls.LoadX509KeyPair(conf.CertFilename, conf.PrivateKeyFilename)
if err == nil {
conf.tlsConfig.Certificates = []tls.Certificate{
cert,
}
} else {
logger.By(hostname).FailExit("connection service can't load certificate %s, private key %s, error - %v", conf.CertFilename, conf.PrivateKeyFilename, err)
}
} else {
logger.By(hostname).Debug("connection service - certificate is not defined")
}
Expand Down Expand Up @@ -106,11 +135,19 @@ func (s *Service) OnFinish() {
close(events)
}

func (s Service) getPool(hostname string) *x509.CertPool {
func (s Service) getTlsConfig(hostname string) *tls.Config {
if conf, ok := s.Configs[hostname]; ok {
return conf.pool
//tlsConfig := new(tls.Config)
//tlsConfig.Certificates = conf.certs
//tlsConfig.RootCAs = conf.pool
//tlsConfig.ClientCAs = conf.pool
//tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
//tlsConfig.CipherSuites = cipherSuites
//tlsConfig.MinVersion = tls.VersionTLS12
//tlsConfig.SessionTicketsDisabled = true
return conf.tlsConfig
} else {
logger.By(hostname).Err("connection service can't find cert by %s", hostname)
logger.By(hostname).Err("connection service can't make tls config by %s", hostname)
return nil
}
}
Expand Down Expand Up @@ -172,8 +209,7 @@ type Config struct {
// количество ip
addressesLen int

// пул сертификатов
pool *x509.CertPool
tlsConfig *tls.Config

hostname string
}
12 changes: 6 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#!/bin/bash
BASE_PATH=`pwd`
VERSION="v.3.1"

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

if [[ -z "$(which git)" ]]
then
echo "sorry, git are not installed"
exit 1
echo "git are not installed!"
exit 2
fi

if [[ -z "$(which go)" ]]
then
echo "sorry, go are not installed"
exit 1
echo "go are not installed!"
exit 3
fi

BASE_PATH=`pwd`
VERSION="v.3.1"
export GOPATH="$BASE_PATH"
export GOBIN="$BASE_PATH/bin/"
go get -d github.com/actionpay/postmanq/cmd
Expand Down
100 changes: 100 additions & 0 deletions recipient/recipient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package recipient

import (
"cmd/go/testdata/src/vend/x/vendor/r"
"net"
"net/textproto"
)

//type RecipientState int
//
//const (
// Ehlo RecipientState = iota
// Helo
// Mail
// Rcpt
// Data
// Rset
// Noop
// Vrfy
// Quit
//)
//
//var (
// nexts = map[RecipientState][]RecipientState {
// Ehlo: []RecipientState{Mail, Rset, Noop, Quit, Vrfy},
// Helo: []RecipientState{Mail, Rset, Noop, Quit, Vrfy},
// Mail: []RecipientState{Rcpt, Rset, Noop, Quit},
// Rcpt: []RecipientState{Data, Rcpt, Rset, Noop, Quit},
// Data: []RecipientState{Mail, Rset, Noop, Quit},
// Rset: []RecipientState{Mail, Rset, Noop, Quit},
// Noop: []RecipientState{},
// Vrfy: []RecipientState{},
// Quit: []RecipientState{},
// }
//)
//
//func (r RecipientState) getName() []byte {}

type Recipient struct {
id int
state State
conn net.Conn
txt *textproto.Conn
}

func newRecipient(id int, events chan *Event) {
mail := new(MailState)
mail.SetPossibles([]State{})

ehlo := new(EhloState)
ehlo.SetNext(mail)
ehlo.SetPossibles([]State{})

conn := new(ConnectState)
conn.SetNext(ehlo)
conn.SetPossibles([]State{})

recipient := &Recipient{
id: id,
state: conn,
}
for event := range events {
recipient.handle(event)
}
}

func (r *Recipient) handle(event *Event) {
r.txt = textproto.NewConn(event.conn)

for {
r.state.SetEvent(event)
status := r.state.Read(r.txt)
goto handleStatus

handleStatus:
switch status {
case SuccessStatus:
r.state.Write(r.txt)
r.state = r.state.GetNext()

case FailureStatus:
r.state.Write(r.txt)

case PossibleStatus:
var possibleStatus StateStatus
for _, possible := range r.state.GetPossibles() {
possible.SetEvent(event)
possibleStatus = possible.Read(r.txt)
if possibleStatus == SuccessStatus {
r.state = possible
status = possibleStatus
goto handleStatus
}
}
r.txt.Cmd("500 Syntax error, command unrecognized")

}
return
}
}
Loading

0 comments on commit bbbb563

Please sign in to comment.