Remove old smtp and port stuff
This commit is contained in:
@@ -1,71 +0,0 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
)
|
||||
|
||||
const DBSchema = `
|
||||
CREATE TABLE IF NOT EXISTS port_attempts(
|
||||
id serial PRIMARY KEY,
|
||||
date timestamptz,
|
||||
remote_ip inet,
|
||||
port varchar(5)
|
||||
);`
|
||||
|
||||
type PostgresStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewPostgresStore(dsn string) (*PostgresStore, error) {
|
||||
db, err := sql.Open("pgx", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &PostgresStore{db: db}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *PostgresStore) InitDB() error {
|
||||
_, err := s.db.Exec(DBSchema)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *PostgresStore) Add(attempt *ConnectionAttempt) error {
|
||||
stmt := `INSERT INTO port_attempts(date, remote_ip, port)
|
||||
VALUES ($1, $2, $3)`
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback() // nolint: errcheck
|
||||
|
||||
_, err = tx.Exec(stmt, time.Now(), attempt.From, attempt.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *PostgresStore) List() ([]*ConnectionAttempt, error) {
|
||||
stmt := `SELECT remote_ip, port FROM port_attempts`
|
||||
rows, err := s.db.Query(stmt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var attempts []*ConnectionAttempt
|
||||
for rows.Next() {
|
||||
var a ConnectionAttempt
|
||||
if err := rows.Scan(&a.From, &a.Port); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attempts = append(attempts, &a)
|
||||
}
|
||||
|
||||
return attempts, nil
|
||||
}
|
@@ -1,102 +0,0 @@
|
||||
package ports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
EnabledPortsTCP []string
|
||||
EnabledPortsUDP []string
|
||||
IP string
|
||||
Logger *zap.SugaredLogger
|
||||
|
||||
store Store
|
||||
}
|
||||
|
||||
func New(store Store) *Server {
|
||||
return &Server{store: store, Logger: zap.NewNop().Sugar()}
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
for _, port := range s.EnabledPortsTCP {
|
||||
portCtx, cancel := context.WithCancel(ctx)
|
||||
go func() {
|
||||
if err := s.doListenTCP(portCtx, port); err != nil {
|
||||
s.Logger.Errorw("Unable to listen to port.", "err", err)
|
||||
}
|
||||
}()
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) AddTCPPort(port string) {
|
||||
s.EnabledPortsTCP = append(s.EnabledPortsTCP, port)
|
||||
}
|
||||
|
||||
func (s *Server) doListenTCP(ctx context.Context, port string) error {
|
||||
lAddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(s.IP, port))
|
||||
if err != nil {
|
||||
s.Logger.Warnw("Error resoling listening addr.", "network", "tcp", "port", port)
|
||||
return fmt.Errorf("error resolving listening address: %w", err)
|
||||
}
|
||||
|
||||
listener, err := net.ListenTCP("tcp", lAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting listener: %w", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
s.Logger.Debug("Listener context cancelled.")
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
s.Logger.Infow("Listening for connections to TCP port.", "port", port)
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.Logger.Infow("Listener shutting down.", "port", port, "network", "tcp")
|
||||
return nil
|
||||
default:
|
||||
s.Logger.Infow("Error accepting connection.", "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
s.Logger.Infow("Got connection on port.", "port", port, "network", "tcp", "remote_addr", conn.RemoteAddr().String())
|
||||
|
||||
if err := conn.SetReadDeadline(time.Now().Add(time.Second * 15)); err != nil {
|
||||
s.Logger.Warnw("Error setting connection read deadline.", "err", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 256)
|
||||
|
||||
_, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
buf = []byte{}
|
||||
}
|
||||
|
||||
attempt := &ConnectionAttempt{
|
||||
Port: port,
|
||||
Network: "tcp",
|
||||
From: conn.RemoteAddr().String(),
|
||||
Data: buf,
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
if err := s.store.Add(attempt); err != nil {
|
||||
s.Logger.Warnw("Error storing attempt in store.", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
// nolint: errcheck
|
||||
package ports_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.t-juice.club/torjus/apiary/honeypot/ports"
|
||||
)
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
store := &ports.MemoryStore{}
|
||||
server := ports.New(store)
|
||||
server.IP = "127.0.0.1"
|
||||
|
||||
server.AddTCPPort("2555")
|
||||
|
||||
go server.Start(ctx)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
rAddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(server.IP, "2555"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error resolving remote address: %s", err)
|
||||
}
|
||||
conn, err := net.DialTCP("tcp", nil, rAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("Dialing server returned error: %s", err)
|
||||
}
|
||||
conn.Write([]byte("LOL"))
|
||||
conn.Close()
|
||||
time.Sleep(1 * time.Second)
|
||||
cancel()
|
||||
|
||||
attempts, err := store.List()
|
||||
if err != nil {
|
||||
t.Fatalf("Getting attempts from store returned error: %s", err)
|
||||
}
|
||||
if len(attempts) != 1 {
|
||||
t.Fatalf("Wrong amount of attempts in store: %d", len(attempts))
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
package ports
|
||||
|
||||
type ConnectionAttempt struct {
|
||||
Port string
|
||||
Network string
|
||||
From string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
Add(attempt *ConnectionAttempt) error
|
||||
List() ([]*ConnectionAttempt, error)
|
||||
}
|
||||
|
||||
type MemoryStore struct {
|
||||
data []*ConnectionAttempt
|
||||
}
|
||||
|
||||
func (s *MemoryStore) Add(attempt *ConnectionAttempt) error {
|
||||
s.data = append(s.data, attempt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) List() ([]*ConnectionAttempt, error) {
|
||||
return s.data, nil
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
package smtp
|
||||
|
||||
import "time"
|
||||
|
||||
type MailData struct {
|
||||
From string
|
||||
Recipients []string
|
||||
Data string
|
||||
|
||||
RemoteAddr string
|
||||
Timestamp time.Time
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
package smtp
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
type MetricsStore struct {
|
||||
backend SMTPStore
|
||||
mailTotal *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func NewMetricsStore(backend SMTPStore) *MetricsStore {
|
||||
store := &MetricsStore{backend: backend}
|
||||
|
||||
store.mailTotal = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "apiary_smtp_received_total",
|
||||
Help: "Total count of received emails.",
|
||||
ConstLabels: prometheus.Labels{"service": "honeypot_smtp"},
|
||||
},
|
||||
[]string{"from"},
|
||||
)
|
||||
prometheus.MustRegister(store.mailTotal)
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
func (s *MetricsStore) AddMailData(data *MailData) error {
|
||||
s.mailTotal.WithLabelValues(data.From).Inc()
|
||||
|
||||
return s.backend.AddMailData(data)
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
package smtp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/mail"
|
||||
"time"
|
||||
|
||||
smtplib "github.com/emersion/go-smtp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type backend struct {
|
||||
honeypot *SMTPHoneypot
|
||||
}
|
||||
|
||||
func (b *backend) Login(state *smtplib.ConnectionState, username, password string) (smtplib.Session, error) {
|
||||
sess := &session{
|
||||
honeypot: b.honeypot,
|
||||
loginUsername: username,
|
||||
loginPassword: password,
|
||||
remoteAddr: state.RemoteAddr.String(),
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
func (b *backend) AnonymousLogin(state *smtplib.ConnectionState) (smtplib.Session, error) {
|
||||
sess := &session{
|
||||
loginAnonymous: true,
|
||||
remoteAddr: state.RemoteAddr.String(),
|
||||
}
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
type session struct {
|
||||
honeypot *SMTPHoneypot
|
||||
|
||||
loginUsername string
|
||||
loginPassword string
|
||||
loginAnonymous bool
|
||||
remoteAddr string
|
||||
|
||||
mail MailData
|
||||
}
|
||||
|
||||
func (sess *session) Reset() {
|
||||
}
|
||||
func (sess *session) Logout() error {
|
||||
return nil
|
||||
}
|
||||
func (sess *session) Mail(from string, opts smtplib.MailOptions) error {
|
||||
sess.mail.From = from
|
||||
return nil
|
||||
}
|
||||
func (sess *session) Rcpt(to string) error {
|
||||
sess.mail.Recipients = append(sess.mail.Recipients, to)
|
||||
return nil
|
||||
}
|
||||
func (sess *session) Data(r io.Reader) error {
|
||||
lr := io.LimitReader(r, 1024*1024)
|
||||
var buf bytes.Buffer
|
||||
n, err := io.Copy(&buf, lr)
|
||||
if err != nil {
|
||||
sess.honeypot.Logger.Debugw("Error reading email data from client.", "error", err, "remote_addr", sess.remoteAddr)
|
||||
}
|
||||
|
||||
if n == 1024*1024 {
|
||||
// There is probably more data, read the rest from the client
|
||||
// We don't really care what happens to this, or if it errors
|
||||
_, _ = io.Copy(io.Discard, r)
|
||||
}
|
||||
|
||||
sess.mail.Data = buf.String()
|
||||
|
||||
var toAddr string
|
||||
var subject string
|
||||
// Parse mail headers
|
||||
msg, err := mail.ReadMessage(&buf)
|
||||
if err != nil {
|
||||
sess.honeypot.Logger.Infow("Unable to parse mail.", "error", err, "remote_addr", sess.remoteAddr)
|
||||
} else {
|
||||
toAddr = msg.Header.Get("To")
|
||||
subject = msg.Header.Get("Subject")
|
||||
}
|
||||
|
||||
if err := sess.honeypot.Store.AddMailData(&sess.mail); err != nil {
|
||||
sess.honeypot.Logger.Warnw("Error storing maildata.", "error", err, "remote_addr", sess.remoteAddr)
|
||||
return smtplib.ErrDataTooLarge
|
||||
}
|
||||
sess.honeypot.Logger.Infow("Mail received", "from", sess.mail.From, "to", toAddr, "subject", subject, "remote_addr", sess.remoteAddr, "size", len(sess.mail.Data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SMTPHoneypot struct {
|
||||
Addr string
|
||||
Logger *zap.SugaredLogger
|
||||
Store SMTPStore
|
||||
server *smtplib.Server
|
||||
}
|
||||
|
||||
func NewSMTPHoneypot() (*SMTPHoneypot, error) {
|
||||
honeypot := &SMTPHoneypot{
|
||||
Logger: zap.NewNop().Sugar(),
|
||||
}
|
||||
|
||||
return honeypot, nil
|
||||
}
|
||||
|
||||
func (sh *SMTPHoneypot) ListenAndServe() error {
|
||||
server := smtplib.NewServer(&backend{
|
||||
honeypot: sh,
|
||||
})
|
||||
server.Addr = sh.Addr
|
||||
server.Domain = "apiary.t-juice.club"
|
||||
server.AllowInsecureAuth = true
|
||||
server.ReadTimeout = 10 * time.Second
|
||||
server.WriteTimeout = 10 * time.Second
|
||||
sh.server = server
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (sh *SMTPHoneypot) Shutdown() error {
|
||||
return sh.server.Close()
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
package smtp
|
||||
|
||||
type SMTPStore interface {
|
||||
AddMailData(mail *MailData) error
|
||||
}
|
||||
|
||||
type DiscardStore struct {
|
||||
}
|
||||
|
||||
func (s *DiscardStore) AddMailData(mail *MailData) error {
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user