|
@@ -1,6 +1,8 @@
|
|
|
-/*
|
|
|
+/**
|
|
|
Go-Guerrilla SMTPd
|
|
|
-An minimalist SMTP server written in Go, made for receiving large volumes of mail.
|
|
|
+A minimalist SMTP server written in Go, made for receiving large volumes of mail.
|
|
|
+Works either as a stand-alone or in conjunction with Nginx SMTP proxy.
|
|
|
+TO DO: add http server for nginx
|
|
|
|
|
|
Copyright (c) 2012 Flashmob, GuerrillaMail.com
|
|
|
|
|
@@ -22,7 +24,12 @@ It's a small SMTP server written in Go, optimized for receiving email.
|
|
|
Written for GuerrillaMail.com which processes tens of thousands of emails
|
|
|
every hour.
|
|
|
|
|
|
-Version: 1.0
|
|
|
+Benchmarking:
|
|
|
+http://www.jrh.org/smtp/index.html
|
|
|
+Test 500 clients:
|
|
|
+$ time smtp-source -c -l 5000 -t [email protected] -s 500 -m 5000 5.9.7.183
|
|
|
+
|
|
|
+Version: 1.1
|
|
|
Author: Flashmob, GuerrillaMail.com
|
|
|
Contact: [email protected]
|
|
|
License: MIT
|
|
@@ -31,15 +38,20 @@ Site: http://www.guerrillamail.com/
|
|
|
|
|
|
See README for more details
|
|
|
|
|
|
-*/
|
|
|
-
|
|
|
-/*
|
|
|
-Install mysql drivers
|
|
|
+To build
|
|
|
+Install the following
|
|
|
$ go get github.com/ziutek/mymysql/thrsafe
|
|
|
$ go get github.com/ziutek/mymysql/autorc
|
|
|
$ go get github.com/ziutek/mymysql/godrv
|
|
|
$ go get github.com/sloonz/go-iconv
|
|
|
+
|
|
|
+TODO: after failing tls,
|
|
|
+
|
|
|
+patch:
|
|
|
+rebuild all: go build -a -v new.go
|
|
|
+
|
|
|
*/
|
|
|
+
|
|
|
package main
|
|
|
|
|
|
import (
|
|
@@ -49,7 +61,6 @@ import (
|
|
|
"crypto/md5"
|
|
|
"crypto/rand"
|
|
|
"crypto/tls"
|
|
|
- // "database/sql"
|
|
|
"encoding/base64"
|
|
|
"encoding/hex"
|
|
|
"encoding/json"
|
|
@@ -65,42 +76,15 @@ import (
|
|
|
"io/ioutil"
|
|
|
"log"
|
|
|
"net"
|
|
|
- //"os"
|
|
|
+ "net/http"
|
|
|
+ "os"
|
|
|
"regexp"
|
|
|
"runtime"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
- "syscall"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
-// defaults. Overwrite any of these in the configure() function which loads them from a json file
|
|
|
-var gConfig = map[string]string{
|
|
|
- "MAX_SMTP_CLIENTS": "10000",
|
|
|
- "GSMTP_MAX_SIZE": "131072",
|
|
|
- "GSMTP_HOST_NAME": "server.example.com", // This should also be set to reflect your RDNS
|
|
|
- "GSMTP_VERBOSE": "Y",
|
|
|
- "GSMTP_TIMEOUT": "100", // how many seconds before timeout.
|
|
|
- "MYSQL_HOST": "127.0.0.1:3306",
|
|
|
- "MYSQL_USER": "gmail_mail",
|
|
|
- "MYSQL_PASS": "ok",
|
|
|
- "MYSQL_DB": "gmail_mail",
|
|
|
- "GM_MAIL_TABLE": "new_mail",
|
|
|
- "GSMTP_USER": "nobody",
|
|
|
- "GSTMP_LISTEN_INTERFACE": "1.0.0.0:25",
|
|
|
- "GSMTP_LOG_FILE": "gosmtp.log",
|
|
|
- "GSMTP_GID": "",
|
|
|
- "GSMTP_UID": "",
|
|
|
- "GSMTP_PUB_KEY": "/etc/ssl/certs/ssl-cert-snakeoil.pem",
|
|
|
- "GSMTP_PRV_KEY": "/etc/ssl/private/ssl-cert-snakeoil.key",
|
|
|
- "GM_ALLOWED_HOSTS": "guerrillamail.de,guerrillamailblock.com",
|
|
|
- "GM_PRIMARY_MAIL_HOST": "guerrillamail.com",
|
|
|
- "GM_CONN_BACKLOG": "100",
|
|
|
- "GM_MAX_CLIENTS": "500",
|
|
|
- "SGID": "1008", // group id
|
|
|
- "SUID": "1008", // user id, from /etc/passwd
|
|
|
-}
|
|
|
-
|
|
|
type Client struct {
|
|
|
state int
|
|
|
helo string
|
|
@@ -114,7 +98,7 @@ type Client struct {
|
|
|
hash string
|
|
|
time int64
|
|
|
tls_on bool
|
|
|
- socket net.Conn
|
|
|
+ conn net.Conn
|
|
|
bufin *bufio.Reader
|
|
|
bufout *bufio.Writer
|
|
|
kill_time int64
|
|
@@ -123,22 +107,59 @@ type Client struct {
|
|
|
savedNotify chan int
|
|
|
}
|
|
|
|
|
|
+var TLSconfig *tls.Config
|
|
|
+var max_size int // max email DATA size
|
|
|
+var timeout time.Duration
|
|
|
+var allowedHosts = make(map[string]bool, 15)
|
|
|
+var sem chan int // currently active clients
|
|
|
+
|
|
|
+var SaveMailChan chan *Client // workers for saving mail
|
|
|
+// defaults. Overwrite any of these in the configure() function which loads them from a json file
|
|
|
+var gConfig = map[string]string{
|
|
|
+ "GSMTP_MAX_SIZE": "131072",
|
|
|
+ "GSMTP_HOST_NAME": "server.example.com", // This should also be set to reflect your RDNS
|
|
|
+ "GSMTP_VERBOSE": "Y",
|
|
|
+ "GSMTP_LOG_FILE": "", // Eg. /var/log/goguerrilla.log or leave blank if no logging
|
|
|
+ "GSMTP_TIMEOUT": "100", // how many seconds before timeout.
|
|
|
+ "MYSQL_HOST": "127.0.0.1:3306",
|
|
|
+ "MYSQL_USER": "gmail_mail",
|
|
|
+ "MYSQL_PASS": "ok",
|
|
|
+ "MYSQL_DB": "gmail_mail",
|
|
|
+ "GM_MAIL_TABLE": "new_mail",
|
|
|
+ "GSTMP_LISTEN_INTERFACE": "0.0.0.0:25",
|
|
|
+ "GSMTP_PUB_KEY": "/etc/ssl/certs/ssl-cert-snakeoil.pem",
|
|
|
+ "GSMTP_PRV_KEY": "/etc/ssl/private/ssl-cert-snakeoil.key",
|
|
|
+ "GM_ALLOWED_HOSTS": "guerrillamail.de,guerrillamailblock.com",
|
|
|
+ "GM_PRIMARY_MAIL_HOST": "guerrillamail.com",
|
|
|
+ "GM_MAX_CLIENTS": "500",
|
|
|
+ "NGINX_AUTH_ENABLED": "N", // Y or N
|
|
|
+ "NGINX_AUTH": "127.0.0.1:8025", // If using Nginx proxy, ip and port to serve Auth requsts
|
|
|
+ "SGID": "1008", // group id
|
|
|
+ "SUID": "1008", // user id, from /etc/passwd
|
|
|
+}
|
|
|
+
|
|
|
type redisClient struct {
|
|
|
count int
|
|
|
conn redis.Conn
|
|
|
time int
|
|
|
}
|
|
|
|
|
|
-var TLSconfig *tls.Config
|
|
|
-var clientChan chan *Client // connection backlog
|
|
|
+func logln(level int, s string) {
|
|
|
|
|
|
-var sem chan int // currently active clients
|
|
|
-var SaveMailChan chan *Client // workers for saving mail
|
|
|
-// hosts allowed in the 'to' address'
|
|
|
-var allowedHosts = make(map[string]bool, 15)
|
|
|
+ if gConfig["GSMTP_VERBOSE"] == "Y" {
|
|
|
+ fmt.Println(s)
|
|
|
+ }
|
|
|
+ if level == 2 {
|
|
|
+ log.Fatalf(s)
|
|
|
+ }
|
|
|
+ if len(gConfig["GSMTP_LOG_FILE"]) > 0 {
|
|
|
+ log.Println(s)
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
func configure() {
|
|
|
var configFile, verbose, iface string
|
|
|
+ log.SetOutput(os.Stdout)
|
|
|
// parse command line arguments
|
|
|
flag.StringVar(&configFile, "config", "goguerrilla.conf", "Path to the configuration file")
|
|
|
flag.StringVar(&verbose, "v", "n", "Verbose, [y | n] ")
|
|
@@ -147,16 +168,13 @@ func configure() {
|
|
|
// load in the config.
|
|
|
b, err := ioutil.ReadFile(configFile)
|
|
|
if err != nil {
|
|
|
- fmt.Println("Could not read config file")
|
|
|
- panic(err)
|
|
|
+ log.Fatalln("Could not read config file")
|
|
|
}
|
|
|
var myConfig map[string]string
|
|
|
err = json.Unmarshal(b, &myConfig)
|
|
|
if err != nil {
|
|
|
- fmt.Println("Could not parse config file")
|
|
|
- panic(err)
|
|
|
+ log.Fatalln("Could not parse config file")
|
|
|
}
|
|
|
-
|
|
|
for k, v := range myConfig {
|
|
|
gConfig[k] = v
|
|
|
}
|
|
@@ -172,161 +190,88 @@ func configure() {
|
|
|
}
|
|
|
var n int
|
|
|
var n_err error
|
|
|
- if n, n_err = strconv.Atoi(gConfig["GM_CONN_BACKLOG"]); n_err != nil {
|
|
|
- n = 50
|
|
|
- }
|
|
|
- // connection backlog list
|
|
|
- clientChan = make(chan *Client, n)
|
|
|
+ // sem is an active clients channel used for counting clients
|
|
|
if n, n_err = strconv.Atoi(gConfig["GM_MAX_CLIENTS"]); n_err != nil {
|
|
|
n = 50
|
|
|
}
|
|
|
// currently active client list
|
|
|
sem = make(chan int, n)
|
|
|
// database writing workers
|
|
|
- SaveMailChan = make(chan *Client, 4)
|
|
|
-
|
|
|
- return
|
|
|
-}
|
|
|
-
|
|
|
-func logln(level int, s string) {
|
|
|
- if level == 2 {
|
|
|
- log.Fatalf(s)
|
|
|
+ SaveMailChan = make(chan *Client, 5)
|
|
|
+ // timeout for reads
|
|
|
+ if n, n_err = strconv.Atoi(gConfig["GSMTP_TIMEOUT"]); n_err != nil {
|
|
|
+ timeout = time.Duration(10)
|
|
|
+ } else {
|
|
|
+ timeout = time.Duration(n)
|
|
|
}
|
|
|
- if gConfig["GSMTP_VERBOSE"] == "Y" {
|
|
|
- fmt.Println(s)
|
|
|
+ // max email size
|
|
|
+ if max_size, n_err = strconv.Atoi(gConfig["GSMTP_MAX_SIZE"]); n_err != nil {
|
|
|
+ max_size = 131072
|
|
|
}
|
|
|
+ // custom log file
|
|
|
+ if len(gConfig["GSMTP_LOG_FILE"]) > 0 {
|
|
|
+ logfile, err := os.OpenFile(gConfig["GSMTP_LOG_FILE"], os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_SYNC, 0600)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal("Unable to open log file ["+gConfig["GSMTP_LOG_FILE"]+"]: ", err)
|
|
|
+ }
|
|
|
+ log.SetOutput(logfile)
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
func main() {
|
|
|
configure()
|
|
|
- logln(1, "Loading priv:"+gConfig["GSMTP_PRV_KEY"]+" and pub:"+gConfig["GSMTP_PRV_KEY"])
|
|
|
cert, err := tls.LoadX509KeyPair(gConfig["GSMTP_PUB_KEY"], gConfig["GSMTP_PRV_KEY"])
|
|
|
if err != nil {
|
|
|
logln(2, fmt.Sprintf("There was a problem with loading the certificate: %s", err))
|
|
|
}
|
|
|
TLSconfig = &tls.Config{Certificates: []tls.Certificate{cert}, ClientAuth: tls.VerifyClientCertIfGiven, ServerName: gConfig["GSMTP_HOST_NAME"]}
|
|
|
TLSconfig.Rand = rand.Reader
|
|
|
+ // start some savemail workers
|
|
|
+ for i := 0; i < 3; i++ {
|
|
|
+ go saveMail()
|
|
|
+ }
|
|
|
+ if gConfig["NGINX_AUTH_ENABLED"] == "Y" {
|
|
|
+ go nginxHTTPAuth()
|
|
|
+ }
|
|
|
+ // Start listening for SMTP connections
|
|
|
listener, err := net.Listen("tcp", gConfig["GSTMP_LISTEN_INTERFACE"])
|
|
|
if err != nil {
|
|
|
- logln(2, fmt.Sprintf("Cannot listen on port, %s", err))
|
|
|
- }
|
|
|
- gid, _ := strconv.ParseInt(gConfig["SGID"], 10, 32)
|
|
|
- uid, _ := strconv.ParseInt(gConfig["SUID"], 10, 32)
|
|
|
- syscall.Setgid(int(gid))
|
|
|
- syscall.Setuid(int(uid))
|
|
|
- logln(1, fmt.Sprintf("server listening on "+gConfig["GSTMP_LISTEN_INTERFACE"]))
|
|
|
- go Serve(clientChan) // Start our SMTP client worker pool
|
|
|
- go saveMail() // start our email saving worker pool
|
|
|
- clientId := int64(1)
|
|
|
+ logln(2, fmt.Sprintf("Cannot listen on port, %v", err))
|
|
|
+ } else {
|
|
|
+ logln(1, fmt.Sprintf("Listening on tcp %s", gConfig["GSTMP_LISTEN_INTERFACE"]))
|
|
|
+ }
|
|
|
+ var clientId int64
|
|
|
+ clientId = 1
|
|
|
for {
|
|
|
conn, err := listener.Accept()
|
|
|
if err != nil {
|
|
|
logln(1, fmt.Sprintf("Accept error: %s", err))
|
|
|
- break
|
|
|
+ continue
|
|
|
}
|
|
|
- logln(1, fmt.Sprintf("server: accepted from %s", conn.RemoteAddr()))
|
|
|
- // place a new client on the channel
|
|
|
- clientChan <- &Client{
|
|
|
- socket: conn,
|
|
|
+ logln(1, fmt.Sprintf(" There are now "+strconv.Itoa(runtime.NumGoroutine())+" serving goroutines"))
|
|
|
+ sem <- 1 // Wait for active queue to drain.
|
|
|
+ go handleClient(&Client{
|
|
|
+ conn: conn,
|
|
|
address: conn.RemoteAddr().String(),
|
|
|
time: time.Now().Unix(),
|
|
|
bufin: bufio.NewReader(conn),
|
|
|
bufout: bufio.NewWriter(conn),
|
|
|
clientId: clientId,
|
|
|
savedNotify: make(chan int),
|
|
|
- }
|
|
|
+ })
|
|
|
clientId++
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func Serve(clientChan chan *Client) {
|
|
|
- for {
|
|
|
- // get new clients off the queue and pass them to the handler
|
|
|
- c := <-clientChan
|
|
|
- sem <- 1 // Wait for active queue to drain.
|
|
|
- go handleClient(c) // Don't wait for handle to finish.
|
|
|
- logln(1, fmt.Sprintf("There are now "+strconv.Itoa(runtime.NumGoroutine())+" goroutines"))
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func closeClient(client *Client) {
|
|
|
- client.socket.Close()
|
|
|
- <-sem // Done; enable next client to run.
|
|
|
-}
|
|
|
-
|
|
|
-func readSmtp(client *Client) (input string, err error) {
|
|
|
- var reply string
|
|
|
- // Command state terminator by default
|
|
|
- suffix := "\r\n"
|
|
|
- if client.state == 2 {
|
|
|
- // DATA state
|
|
|
- suffix = "\r\n.\r\n"
|
|
|
- }
|
|
|
- for err == nil {
|
|
|
- client.socket.SetDeadline(time.Now().Add(100 * time.Second))
|
|
|
- reply, err = client.bufin.ReadString('\n')
|
|
|
- if reply != "" {
|
|
|
- input = input + reply
|
|
|
- if client.state == 2 {
|
|
|
- // Extract the subject while we are at it.
|
|
|
- scanSubject(client, reply)
|
|
|
- }
|
|
|
- }
|
|
|
- if err != nil {
|
|
|
- break
|
|
|
- }
|
|
|
- if strings.HasSuffix(input, suffix) {
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- return input, err
|
|
|
-}
|
|
|
-
|
|
|
-// Scan the data part for a Subject line. Can be a multi-line
|
|
|
-func scanSubject(client *Client, reply string) {
|
|
|
- if client.subject == "" && (len(reply) > 8) {
|
|
|
- test := strings.ToUpper(reply[0:9])
|
|
|
- if i := strings.Index(test, "SUBJECT: "); i == 0 {
|
|
|
- // first line with \r\n
|
|
|
- client.subject = reply[9:]
|
|
|
- }
|
|
|
- } else if strings.HasSuffix(client.subject, "\r\n") {
|
|
|
- // chop off the \r\n
|
|
|
- client.subject = client.subject[0 : len(client.subject)-2]
|
|
|
- if (strings.HasPrefix(reply, " ")) || (strings.HasPrefix(reply, "\t")) {
|
|
|
- // subject is multi-line
|
|
|
- client.subject = client.subject + reply[1:]
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func responseWrite(client *Client) (err error) {
|
|
|
- var size int
|
|
|
- client.socket.SetDeadline(time.Now().Add(100 * time.Second))
|
|
|
- size, err = client.bufout.WriteString(client.response)
|
|
|
- client.bufout.Flush()
|
|
|
- client.response = client.response[size:]
|
|
|
- return err
|
|
|
-}
|
|
|
-
|
|
|
-func responseAdd(client *Client, line string) {
|
|
|
- client.response = line + "\r\n"
|
|
|
-}
|
|
|
-func responseClear(client *Client) {
|
|
|
- client.response = ""
|
|
|
-}
|
|
|
-
|
|
|
-func killClient(client *Client) {
|
|
|
- client.kill_time = time.Now().Unix()
|
|
|
-}
|
|
|
-
|
|
|
func handleClient(client *Client) {
|
|
|
- var input_hist string
|
|
|
defer closeClient(client)
|
|
|
+ // defer closeClient(client)
|
|
|
greeting := "220 " + gConfig["GSMTP_HOST_NAME"] +
|
|
|
" SMTP Guerrilla-SMTPd #" + strconv.FormatInt(client.clientId, 10) + " (" + strconv.Itoa(len(sem)) + ") " + time.Now().Format(time.RFC1123Z)
|
|
|
advertiseTls := "250-STARTTLS\r\n"
|
|
|
- for i := 0; i < 10; i++ {
|
|
|
+ for i := 0; i < 100; i++ {
|
|
|
switch client.state {
|
|
|
case 0:
|
|
|
responseAdd(client, greeting)
|
|
@@ -334,6 +279,7 @@ func handleClient(client *Client) {
|
|
|
case 1:
|
|
|
input, err := readSmtp(client)
|
|
|
if err != nil {
|
|
|
+ logln(1, fmt.Sprintf("Read error: %v", err))
|
|
|
if err == io.EOF {
|
|
|
// client closed the connection already
|
|
|
return
|
|
@@ -345,7 +291,6 @@ func handleClient(client *Client) {
|
|
|
break
|
|
|
}
|
|
|
input = strings.Trim(input, " \n\r")
|
|
|
- input_hist = input_hist + input + "\n"
|
|
|
cmd := strings.ToUpper(input)
|
|
|
switch {
|
|
|
case strings.Index(cmd, "HELO") == 0:
|
|
@@ -357,15 +302,19 @@ func handleClient(client *Client) {
|
|
|
if len(input) > 5 {
|
|
|
client.helo = input[5:]
|
|
|
}
|
|
|
- if client.tls_on {
|
|
|
- advertiseTls = ""
|
|
|
- }
|
|
|
responseAdd(client, "250-"+gConfig["GSMTP_HOST_NAME"]+" Hello "+client.helo+"["+client.address+"]"+"\r\n"+"250-SIZE "+gConfig["GSMTP_MAX_SIZE"]+"\r\n"+advertiseTls+"250 HELP")
|
|
|
case strings.Index(cmd, "MAIL FROM:") == 0:
|
|
|
if len(input) > 10 {
|
|
|
client.mail_from = input[10:]
|
|
|
}
|
|
|
responseAdd(client, "250 Ok")
|
|
|
+ case strings.Index(cmd, "XCLIENT") == 0:
|
|
|
+ // Nginx sends this
|
|
|
+ // XCLIENT ADDR=212.96.64.216 NAME=[UNAVAILABLE]
|
|
|
+ client.address = input[13:]
|
|
|
+ client.address = client.address[0:strings.Index(client.address, " ")]
|
|
|
+ fmt.Println("client address:[" + client.address + "]")
|
|
|
+ responseAdd(client, "250 OK")
|
|
|
case strings.Index(cmd, "RCPT TO:") == 0:
|
|
|
if len(input) > 8 {
|
|
|
client.rcpt_to = input[8:]
|
|
@@ -388,10 +337,10 @@ func handleClient(client *Client) {
|
|
|
responseAdd(client, "221 Bye")
|
|
|
killClient(client)
|
|
|
default:
|
|
|
- responseAdd(client, fmt.Sprintf("500 unrecognized command %v", err))
|
|
|
+ responseAdd(client, fmt.Sprintf("500 unrecognized command"))
|
|
|
client.errors++
|
|
|
if client.errors > 3 {
|
|
|
- responseAdd(client, fmt.Sprintf("500 Too many unrecognized commands %v", err))
|
|
|
+ responseAdd(client, fmt.Sprintf("500 Too many unrecognized commands"))
|
|
|
killClient(client)
|
|
|
}
|
|
|
}
|
|
@@ -404,23 +353,31 @@ func handleClient(client *Client) {
|
|
|
SaveMailChan <- client
|
|
|
// wait for the save to complete
|
|
|
status := <-client.savedNotify
|
|
|
+
|
|
|
if status == 1 {
|
|
|
responseAdd(client, "250 OK : queued as "+client.hash)
|
|
|
} else {
|
|
|
responseAdd(client, "554 Error: transaction failed, blame it on the weather")
|
|
|
}
|
|
|
+ } else {
|
|
|
+ logln(1, fmt.Sprintf("DATA read error: %v", err))
|
|
|
}
|
|
|
client.state = 1
|
|
|
case 3:
|
|
|
// upgrade to TLS
|
|
|
var tlsConn *tls.Conn
|
|
|
- tlsConn = tls.Server(client.socket, TLSconfig)
|
|
|
- tlsConn.Handshake() // not necessary to call here, but might as well
|
|
|
- client.socket = net.Conn(tlsConn)
|
|
|
- client.bufin = bufio.NewReader(client.socket)
|
|
|
- client.bufout = bufio.NewWriter(client.socket)
|
|
|
+ tlsConn = tls.Server(client.conn, TLSconfig)
|
|
|
+ err := tlsConn.Handshake() // not necessary to call here, but might as well
|
|
|
+ if err == nil {
|
|
|
+ client.conn = net.Conn(tlsConn)
|
|
|
+ client.bufin = bufio.NewReader(client.conn)
|
|
|
+ client.bufout = bufio.NewWriter(client.conn)
|
|
|
+ client.tls_on = true
|
|
|
+ } else {
|
|
|
+ logln(1, fmt.Sprintf("Could not TLS handshake:%v", err))
|
|
|
+ }
|
|
|
+ advertiseTls = ""
|
|
|
client.state = 1
|
|
|
- client.tls_on = true
|
|
|
}
|
|
|
// Send a response back to the client
|
|
|
err := responseWrite(client)
|
|
@@ -438,6 +395,77 @@ func handleClient(client *Client) {
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func responseAdd(client *Client, line string) {
|
|
|
+ client.response = line + "\r\n"
|
|
|
+}
|
|
|
+func closeClient(client *Client) {
|
|
|
+ client.conn.Close()
|
|
|
+ <-sem // Done; enable next client to run.
|
|
|
+}
|
|
|
+func killClient(client *Client) {
|
|
|
+ client.kill_time = time.Now().Unix()
|
|
|
+}
|
|
|
+
|
|
|
+func readSmtp(client *Client) (input string, err error) {
|
|
|
+ var reply string
|
|
|
+ // Command state terminator by default
|
|
|
+ suffix := "\r\n"
|
|
|
+ if client.state == 2 {
|
|
|
+ // DATA state
|
|
|
+ suffix = "\r\n.\r\n"
|
|
|
+ }
|
|
|
+ for err == nil {
|
|
|
+ client.conn.SetDeadline(time.Now().Add(timeout * time.Second))
|
|
|
+ reply, err = client.bufin.ReadString('\n')
|
|
|
+ if reply != "" {
|
|
|
+ input = input + reply
|
|
|
+ if len(input) > max_size {
|
|
|
+ err = errors.New("Maximum DATA size exceeded (" + strconv.Itoa(max_size) + ")")
|
|
|
+ return input, err
|
|
|
+ }
|
|
|
+ if client.state == 2 {
|
|
|
+ // Extract the subject while we are at it.
|
|
|
+ scanSubject(client, reply)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if strings.HasSuffix(input, suffix) {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return input, err
|
|
|
+}
|
|
|
+
|
|
|
+// Scan the data part for a Subject line. Can be a multi-line
|
|
|
+func scanSubject(client *Client, reply string) {
|
|
|
+ if client.subject == "" && (len(reply) > 8) {
|
|
|
+ test := strings.ToUpper(reply[0:9])
|
|
|
+ if i := strings.Index(test, "SUBJECT: "); i == 0 {
|
|
|
+ // first line with \r\n
|
|
|
+ client.subject = reply[9:]
|
|
|
+ }
|
|
|
+ } else if strings.HasSuffix(client.subject, "\r\n") {
|
|
|
+ // chop off the \r\n
|
|
|
+ client.subject = client.subject[0 : len(client.subject)-2]
|
|
|
+ if (strings.HasPrefix(reply, " ")) || (strings.HasPrefix(reply, "\t")) {
|
|
|
+ // subject is multi-line
|
|
|
+ client.subject = client.subject + reply[1:]
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func responseWrite(client *Client) (err error) {
|
|
|
+ var size int
|
|
|
+ client.conn.SetDeadline(time.Now().Add(timeout * time.Second))
|
|
|
+ size, err = client.bufout.WriteString(client.response)
|
|
|
+ client.bufout.Flush()
|
|
|
+ client.response = client.response[size:]
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
func saveMail() {
|
|
@@ -461,7 +489,6 @@ func saveMail() {
|
|
|
if sql_err != nil {
|
|
|
logln(2, fmt.Sprintf("Sql statement incorrect: %s", sql_err))
|
|
|
}
|
|
|
- //defer db.Close()
|
|
|
|
|
|
// receives values from the channel repeatedly until it is closed.
|
|
|
for {
|
|
@@ -494,9 +521,8 @@ func saveMail() {
|
|
|
client.data = ""
|
|
|
body = "redis"
|
|
|
}
|
|
|
- //fmt.Println(do_reply, do_err)
|
|
|
} else {
|
|
|
- fmt.Println("redis err", redis_err)
|
|
|
+ logln(1, fmt.Sprintf("redis: %v", redis_err))
|
|
|
}
|
|
|
// bind data to cursor
|
|
|
ins.Bind(
|
|
@@ -517,7 +543,7 @@ func saveMail() {
|
|
|
logln(1, "Email saved "+client.hash+" len:"+strconv.Itoa(length))
|
|
|
_, _, err = incr.Exec()
|
|
|
if err != nil {
|
|
|
- fmt.Println(err)
|
|
|
+ logln(1, fmt.Sprintf("Failed to incr count:", err))
|
|
|
}
|
|
|
client.savedNotify <- 1
|
|
|
}
|
|
@@ -539,15 +565,6 @@ func (c *redisClient) redisConnection() (err error) {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func mysqlTest() {
|
|
|
- //var mysqlCon *sql.DB
|
|
|
- //mysqlCon, err := sql.Open("mymysql", gConfig["MYSQL_DB"]+"/"+gConfig["MYSQL_USER"]+"/"+gConfig["MYSQL_PASS"])
|
|
|
- //if err != nil {
|
|
|
- // log.Fatalf("Cannot open Mysql connection: %s", err)
|
|
|
- //}
|
|
|
- // defer mysqlCon.Close()
|
|
|
-}
|
|
|
-
|
|
|
func validateEmailData(client *Client) (user string, host string, addr_err error) {
|
|
|
if user, host, addr_err = extractEmail(client.mail_from); addr_err != nil {
|
|
|
return user, host, addr_err
|
|
@@ -570,9 +587,9 @@ func extractEmail(str string) (name string, host string, err error) {
|
|
|
host = validHost(matched[2])
|
|
|
name = matched[1]
|
|
|
} else {
|
|
|
- if res := strings.Split(name, "@"); len(res) > 1 {
|
|
|
- name = matched[0]
|
|
|
- host = validHost(matched[1])
|
|
|
+ if res := strings.Split(str, "@"); len(res) > 1 {
|
|
|
+ name = res[0]
|
|
|
+ host = validHost(res[1])
|
|
|
}
|
|
|
}
|
|
|
if host == "" || name == "" {
|
|
@@ -689,3 +706,27 @@ func md5hex(str string) string {
|
|
|
sum := h.Sum([]byte{})
|
|
|
return hex.EncodeToString(sum)
|
|
|
}
|
|
|
+
|
|
|
+// If running Nginx as a proxy, give Nginx the IP address and port for the SMTP server
|
|
|
+// Primary use of Nginx is to terminate TLS so that Go doesn't need to deal with it.
|
|
|
+// This could perform auth and load balancing too
|
|
|
+// See http://wiki.nginx.org/MailCoreModule
|
|
|
+func nginxHTTPAuth() {
|
|
|
+ parts := strings.Split(gConfig["GSTMP_LISTEN_INTERFACE"], ":")
|
|
|
+ gConfig["HTTP_AUTH_HOST"] = parts[0]
|
|
|
+ gConfig["HTTP_AUTH_PORT"] = parts[1]
|
|
|
+ fmt.Println(parts)
|
|
|
+ http.HandleFunc("/", nginxHTTPAuthHandler)
|
|
|
+ err := http.ListenAndServe(gConfig["NGINX_AUTH"], nil)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal("ListenAndServe: ", err)
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func nginxHTTPAuthHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
+ w.Header().Add("Auth-Status", "OK")
|
|
|
+ w.Header().Add("Auth-Server", gConfig["HTTP_AUTH_HOST"])
|
|
|
+ w.Header().Add("Auth-Port", gConfig["HTTP_AUTH_PORT"])
|
|
|
+ fmt.Fprint(w, "")
|
|
|
+}
|