浏览代码

first commit

Clomode 13 年之前
当前提交
3cb22d40ef
共有 3 个文件被更改,包括 714 次插入0 次删除
  1. 0 0
      README.md
  2. 23 0
      goguerrilla.conf.sample
  3. 691 0
      goguerrilla.go

+ 0 - 0
README.md


+ 23 - 0
goguerrilla.conf.sample

@@ -0,0 +1,23 @@
+{
+    "GM_ALLOWED_HOSTS":"example.com,sample.com,foo.com,bar.com",
+    "GM_MAIL_TABLE":"new_mail",
+    "GM_PRIMARY_MAIL_HOST":"mail.example.com",
+    "GSMTP_GID":"",
+    "GSMTP_HOST_NAME":"mail.example.com",
+    "GSMTP_LOG_FILE":"/dev/stdout",
+    "GSMTP_MAX_SIZE":"131072",
+    "GSMTP_PRV_KEY":"/etc/ssl/private/example.com.key",
+    "GSMTP_PUB_KEY":"/etc/ssl/certs/example.com.crt",
+    "GSMTP_TIMEOUT":"100",
+    "GSMTP_VERBOSE":"N",
+    "GSTMP_LISTEN_INTERFACE":"5.9.7.183:25",
+    "MAX_SMTP_CLIENTS":"10000",
+    "MYSQL_DB":"gmail_mail",
+    "MYSQL_HOST":"127.0.0.1:3306",
+    "MYSQL_PASS":"$ecure1t",
+    "MYSQL_USER":"gmail_mail",
+    "GM_CONN_BACKLOG":"100",
+    "GM_MAX_CLIENTS":"500",
+    "SGID":"508",
+	"GUID":"504"
+}

+ 691 - 0
goguerrilla.go

@@ -0,0 +1,691 @@
+/*
+Go-Guerrilla SMTPd
+An minimalist SMTP server written in Go, made for receiving large volumes of mail.
+
+Copyright (c) 2012 Flashmob, GuerrillaMail.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+What is Go Guerrilla SMTPd?
+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
+Author: Flashmob, GuerrillaMail.com
+Contact: [email protected]
+License: MIT
+Repository: https://github.com/flashmob/Go-Guerrilla-SMTPd
+Site: http://www.guerrillamail.com/
+
+See README for more details
+
+*/
+
+/*
+Install mysql drivers
+$ 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
+*/
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"compress/zlib"
+	"crypto/md5"
+	"crypto/rand"
+	"crypto/tls"
+	// "database/sql"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"flag"
+	"fmt"
+	"github.com/garyburd/redigo/redis"
+	"github.com/sloonz/go-iconv"
+	"github.com/sloonz/go-qprintable"
+	"github.com/ziutek/mymysql/autorc"
+	_ "github.com/ziutek/mymysql/godrv"
+	"io"
+	"io/ioutil"
+	"log"
+	"net"
+	//"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
+	mail_from   string
+	rcpt_to     string
+	read_buffer string
+	response    string
+	address     string
+	data        string
+	subject     string
+	hash        string
+	time        int64
+	tls_on      bool
+	socket      net.Conn
+	bufin       *bufio.Reader
+	bufout      *bufio.Writer
+	kill_time   int64
+	errors      int
+	clientId    int64
+	savedNotify chan int
+}
+
+type redisClient struct {
+	count int
+	conn  redis.Conn
+	time  int
+}
+
+var TLSconfig *tls.Config
+var clientChan chan *Client // connection backlog
+
+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)
+
+func configure() {
+	var configFile, verbose, iface string
+	// parse command line arguments
+	flag.StringVar(&configFile, "config", "goguerrilla.conf", "Path to the configuration file")
+	flag.StringVar(&verbose, "v", "n", "Verbose, [y | n] ")
+	flag.StringVar(&iface, "if", "", "Interface and port to listen on, eg. 127.0.0.1:2525 ")
+	flag.Parse()
+	// load in the config.
+	b, err := ioutil.ReadFile(configFile)
+	if err != nil {
+		fmt.Println("Could not read config file")
+		panic(err)
+	}
+	var myConfig map[string]string
+	err = json.Unmarshal(b, &myConfig)
+	if err != nil {
+		fmt.Println("Could not parse config file")
+		panic(err)
+	}
+
+	for k, v := range myConfig {
+		gConfig[k] = v
+	}
+	gConfig["GSMTP_VERBOSE"] = strings.ToUpper(verbose)
+	if len(iface) > 0 {
+		gConfig["GSTMP_LISTEN_INTERFACE"] = iface
+	}
+	// map the allow hosts for easy lookup
+	if arr := strings.Split(gConfig["GM_ALLOWED_HOSTS"], ","); len(arr) > 0 {
+		for i := 0; i < len(arr); i++ {
+			allowedHosts[arr[i]] = true
+		}
+	}
+	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)
+	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)
+	}
+	if gConfig["GSMTP_VERBOSE"] == "Y" {
+		fmt.Println(s)
+	}
+}
+
+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
+	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)
+	for {
+		conn, err := listener.Accept()
+		if err != nil {
+			logln(1, fmt.Sprintf("Accept error: %s", err))
+			break
+		}
+		logln(1, fmt.Sprintf("server: accepted from %s", conn.RemoteAddr()))
+		// place a new client on the channel
+		clientChan <- &Client{
+			socket:      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)
+	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++ {
+		switch client.state {
+		case 0:
+			responseAdd(client, greeting)
+			client.state = 1
+		case 1:
+			input, err := readSmtp(client)
+			if err != nil {
+				if err == io.EOF {
+					// client closed the connection already
+					return
+				}
+				if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
+					// too slow, timeout
+					return
+				}
+				break
+			}
+			input = strings.Trim(input, " \n\r")
+			input_hist = input_hist + input + "\n"
+			cmd := strings.ToUpper(input)
+			switch {
+			case strings.Index(cmd, "HELO") == 0:
+				if len(input) > 5 {
+					client.helo = input[5:]
+				}
+				responseAdd(client, "250 "+gConfig["GSMTP_HOST_NAME"]+" Hello ")
+			case strings.Index(cmd, "EHLO") == 0:
+				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, "RCPT TO:") == 0:
+				if len(input) > 8 {
+					client.rcpt_to = input[8:]
+				}
+				responseAdd(client, "250 Accepted")
+			case strings.Index(cmd, "NOOP") == 0:
+				responseAdd(client, "250 OK")
+			case strings.Index(cmd, "RSET") == 0:
+				client.mail_from = ""
+				client.rcpt_to = ""
+				responseAdd(client, "250 OK")
+			case strings.Index(cmd, "DATA") == 0:
+				responseAdd(client, "354 Enter message, ending with \".\" on a line by itself")
+				client.state = 2
+			case (strings.Index(cmd, "STARTTLS") == 0) && !client.tls_on:
+				responseAdd(client, "220 Ready to start TLS")
+				// go to start TLS state
+				client.state = 3
+			case strings.Index(cmd, "QUIT") == 0:
+				responseAdd(client, "221 Bye")
+				killClient(client)
+			default:
+				responseAdd(client, fmt.Sprintf("500 unrecognized command %v", err))
+				client.errors++
+				if client.errors > 3 {
+					responseAdd(client, fmt.Sprintf("500 Too many unrecognized commands %v", err))
+					killClient(client)
+				}
+			}
+		case 2:
+			var err error
+			client.data, err = readSmtp(client)
+			if err == nil {
+				// to do: timeout when adding to SaveMailChan
+				// place on the channel so that one of the save mail workers can pick it up
+				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")
+				}
+			}
+			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)
+			client.state = 1
+			client.tls_on = true
+		}
+		// Send a response back to the client
+		err := responseWrite(client)
+		if err != nil {
+			if err == io.EOF {
+				// client closed the connection already
+				return
+			}
+			if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
+				// too slow, timeout
+				return
+			}
+		}
+		if client.kill_time > 1 {
+			return
+		}
+	}
+}
+
+func saveMail() {
+	var to string
+	var err error
+	var body string
+	var redis_err error
+	var length int
+	redis := &redisClient{}
+	db := autorc.New("tcp", "", gConfig["MYSQL_HOST"], gConfig["MYSQL_USER"], gConfig["MYSQL_PASS"], gConfig["MYSQL_DB"])
+	db.Register("set names utf8")
+	sql := "INSERT INTO " + gConfig["GM_MAIL_TABLE"] + " "
+	sql += "(`date`, `to`, `from`, `subject`, `body`, `charset`, `mail`, `spam_score`, `hash`, `content_type`, `recipient`, `has_attach`, `ip_addr`)"
+	sql += " values (NOW(), ?, ?, ?, ? , 'UTF-8' , ?, 0, ?, '', ?, 0, ?)"
+	ins, sql_err := db.Prepare(sql)
+	if sql_err != nil {
+		logln(2, fmt.Sprintf("Sql statement incorrect: %s", sql_err))
+	}
+	sql = "UPDATE gm2_setting SET `setting_value` = `setting_value`+1 WHERE `setting_name`='received_emails' LIMIT 1"
+	incr, sql_err := db.Prepare(sql)
+	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 {
+		client := <-SaveMailChan
+		if user, _, addr_err := validateEmailData(client); addr_err != nil { // user, host, addr_err
+			logln(1, fmt.Sprintln("mail_from didnt validate: %v", addr_err)+" client.mail_from:"+client.mail_from)
+			// notify client that a save completed, -1 = error
+			client.savedNotify <- -1
+			continue
+		} else {
+			to = user + "@" + gConfig["GM_PRIMARY_MAIL_HOST"]
+		}
+		length = len(client.data)
+		client.subject = mimeHeaderDecode(client.subject)
+		client.hash = md5hex(to + client.mail_from + client.subject + strconv.FormatInt(time.Now().UnixNano(), 10))
+		// Add extra headers
+		add_head := ""
+		add_head += "Delivered-To: " + to + "\r\n"
+		add_head += "Received: from " + client.helo + " (" + client.helo + "  [" + client.address + "])\r\n"
+		add_head += "	by " + gConfig["GSMTP_HOST_NAME"] + " with SMTP id " + client.hash + "@" +
+			gConfig["GSMTP_HOST_NAME"] + ";\r\n"
+		add_head += "	" + time.Now().Format(time.RFC1123Z) + "\r\n"
+		// compress to save space
+		client.data = compress(add_head + client.data)
+		body = "gzencode"
+		redis_err = redis.redisConnection()
+		if redis_err == nil {
+			_, do_err := redis.conn.Do("SETEX", client.hash, 3600, client.data)
+			if do_err == nil {
+				client.data = ""
+				body = "redis"
+			}
+			//fmt.Println(do_reply, do_err)
+		} else {
+			fmt.Println("redis err", redis_err)
+		}
+		// bind data to cursor
+		ins.Bind(
+			to,
+			client.mail_from,
+			client.subject,
+			body,
+			client.data,
+			client.hash,
+			to,
+			client.address)
+		// save, discard result
+		_, _, err = ins.Exec()
+		if err != nil {
+			logln(1, fmt.Sprintf("Database error, %v %v", err))
+			client.savedNotify <- -1
+		} else {
+			logln(1, "Email saved "+client.hash+" len:"+strconv.Itoa(length))
+			_, _, err = incr.Exec()
+			if err != nil {
+				fmt.Println(err)
+			}
+			client.savedNotify <- 1
+		}
+	}
+}
+
+func (c *redisClient) redisConnection() (err error) {
+	if c.count > 100 {
+		c.conn.Close()
+		c.count = 0
+	}
+	if c.count == 0 {
+		c.conn, err = redis.Dial("tcp", ":6379")
+		if err != nil {
+			// handle error
+			return err
+		}
+	}
+	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
+	}
+	client.mail_from = user + "@" + host
+	if user, host, addr_err = extractEmail(client.rcpt_to); addr_err != nil {
+		return user, host, addr_err
+	}
+	client.rcpt_to = user + "@" + host
+	// check if on allowed hosts
+	if allowed := allowedHosts[host]; !allowed {
+		return user, host, errors.New("invalid host:" + host)
+	}
+	return user, host, addr_err
+}
+
+func extractEmail(str string) (name string, host string, err error) {
+	re, _ := regexp.Compile(`<(.+?)@(.+?)>`) // go home regex, you're drunk!
+	if matched := re.FindStringSubmatch(str); len(matched) > 2 {
+		host = validHost(matched[2])
+		name = matched[1]
+	} else {
+		if res := strings.Split(name, "@"); len(res) > 1 {
+			name = matched[0]
+			host = validHost(matched[1])
+		}
+	}
+	if host == "" || name == "" {
+		err = errors.New("Invalid address, [" + name + "@" + host + "] address:" + str)
+	}
+	return name, host, err
+}
+
+// Decode strings in Mime header format
+// eg. =?ISO-2022-JP?B?GyRCIVo9dztSOWJAOCVBJWMbKEI=?=
+func mimeHeaderDecode(str string) string {
+	reg, _ := regexp.Compile(`=\?(.+?)\?([QBqp])\?(.+?)\?=`)
+	matched := reg.FindAllStringSubmatch(str, -1)
+	var charset, encoding, payload string
+	if matched != nil {
+		for i := 0; i < len(matched); i++ {
+			if len(matched[i]) > 2 {
+				charset = matched[i][1]
+				encoding = strings.ToUpper(matched[i][2])
+				payload = matched[i][3]
+				switch encoding {
+				case "B":
+					str = strings.Replace(str, matched[i][0], mailTransportDecode(payload, "base64", charset), 1)
+				case "Q":
+					str = strings.Replace(str, matched[i][0], mailTransportDecode(payload, "quoted-printable", charset), 1)
+				}
+			}
+		}
+	}
+	return str
+}
+
+func validHost(host string) string {
+	host = strings.Trim(host, " ")
+	re, _ := regexp.Compile(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
+	if re.MatchString(host) {
+		return host
+	}
+	return ""
+}
+
+// decode from 7bit to 8bit UTF-8
+// encoding_type can be "base64" or "quoted-printable"
+func mailTransportDecode(str string, encoding_type string, charset string) string {
+	if charset == "" {
+		charset = "UTF-8"
+	} else {
+		charset = strings.ToUpper(charset)
+	}
+	if encoding_type == "base64" {
+		str = fromBase64(str)
+	} else if encoding_type == "quoted-printable" {
+		str = fromQuotedP(str)
+	}
+	if charset != "UTF-8" {
+		charset = fixCharset(charset)
+		// eg. charset can be "ISO-2022-JP"
+		convstr, err := iconv.Conv(str, "UTF-8", charset)
+		if err == nil {
+			return convstr
+		}
+	}
+	return str
+}
+
+func fromBase64(data string) string {
+	buf := bytes.NewBufferString(data)
+	decoder := base64.NewDecoder(base64.StdEncoding, buf)
+	res, _ := ioutil.ReadAll(decoder)
+	return string(res)
+}
+
+func fromQuotedP(data string) string {
+	buf := bytes.NewBufferString(data)
+	decoder := qprintable.NewDecoder(qprintable.BinaryEncoding, buf)
+	res, _ := ioutil.ReadAll(decoder)
+	return string(res)
+}
+
+func compress(s string) string {
+	var b bytes.Buffer
+	w, _ := zlib.NewWriterLevel(&b, zlib.BestSpeed) // flate.BestCompression
+	w.Write([]byte(s))
+	w.Close()
+	return b.String()
+}
+
+func fixCharset(charset string) string {
+	reg, _ := regexp.Compile(`[_:.\/\\]`)
+	fixed_charset := reg.ReplaceAllString(charset, "-")
+	// Fix charset
+	// borrowed from http://squirrelmail.svn.sourceforge.net/viewvc/squirrelmail/trunk/squirrelmail/include/languages.php?revision=13765&view=markup
+	// OE ks_c_5601_1987 > cp949
+	fixed_charset = strings.Replace(fixed_charset, "ks-c-5601-1987", "cp949", -1)
+	// Moz x-euc-tw > euc-tw
+	fixed_charset = strings.Replace(fixed_charset, "x-euc", "euc", -1)
+	// Moz x-windows-949 > cp949
+	fixed_charset = strings.Replace(fixed_charset, "x-windows_", "cp", -1)
+	// windows-125x and cp125x charsets
+	fixed_charset = strings.Replace(fixed_charset, "windows-", "cp", -1)
+	// ibm > cp
+	fixed_charset = strings.Replace(fixed_charset, "ibm", "cp", -1)
+	// iso-8859-8-i -> iso-8859-8
+	fixed_charset = strings.Replace(fixed_charset, "iso-8859-8-i", "iso-8859-8", -1)
+	if charset != fixed_charset {
+		return fixed_charset
+	}
+	return charset
+}
+
+func md5hex(str string) string {
+	h := md5.New()
+	h.Write([]byte(str))
+	sum := h.Sum([]byte{})
+	return hex.EncodeToString(sum)
+}