goguerrilla.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. /**
  2. Go-Guerrilla SMTPd
  3. A minimalist SMTP server written in Go, made for receiving large volumes of mail.
  4. Works either as a stand-alone or in conjunction with Nginx SMTP proxy.
  5. TO DO: add http server for nginx
  6. Copyright (c) 2012 Flashmob, GuerrillaMail.com
  7. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  8. documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  9. rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  10. permit persons to whom the Software is furnished to do so, subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  12. Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14. WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  15. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  17. What is Go Guerrilla SMTPd?
  18. It's a small SMTP server written in Go, optimized for receiving email.
  19. Written for GuerrillaMail.com which processes tens of thousands of emails
  20. every hour.
  21. Benchmarking:
  22. http://www.jrh.org/smtp/index.html
  23. Test 500 clients:
  24. $ time smtp-source -c -l 5000 -t [email protected] -s 500 -m 5000 5.9.7.183
  25. Version: 1.1
  26. Author: Flashmob, GuerrillaMail.com
  27. Contact: [email protected]
  28. License: MIT
  29. Repository: https://github.com/flashmob/Go-Guerrilla-SMTPd
  30. Site: http://www.guerrillamail.com/
  31. See README for more details
  32. To build
  33. Install the following
  34. $ go get github.com/ziutek/mymysql/thrsafe
  35. $ go get github.com/ziutek/mymysql/autorc
  36. $ go get github.com/ziutek/mymysql/godrv
  37. $ go get github.com/sloonz/go-iconv
  38. TODO: after failing tls,
  39. patch:
  40. rebuild all: go build -a -v new.go
  41. */
  42. package main
  43. import (
  44. "bufio"
  45. "bytes"
  46. "compress/zlib"
  47. "crypto/md5"
  48. "crypto/rand"
  49. "crypto/tls"
  50. "encoding/base64"
  51. "encoding/hex"
  52. "encoding/json"
  53. "errors"
  54. "flag"
  55. "fmt"
  56. "github.com/garyburd/redigo/redis"
  57. "github.com/sloonz/go-iconv"
  58. "github.com/sloonz/go-qprintable"
  59. "github.com/ziutek/mymysql/autorc"
  60. _ "github.com/ziutek/mymysql/godrv"
  61. "io"
  62. "io/ioutil"
  63. "log"
  64. "net"
  65. "net/http"
  66. "os"
  67. "regexp"
  68. "runtime"
  69. "strconv"
  70. "strings"
  71. "time"
  72. )
  73. type Client struct {
  74. state int
  75. helo string
  76. mail_from string
  77. rcpt_to string
  78. read_buffer string
  79. response string
  80. address string
  81. data string
  82. subject string
  83. hash string
  84. time int64
  85. tls_on bool
  86. conn net.Conn
  87. bufin *bufio.Reader
  88. bufout *bufio.Writer
  89. kill_time int64
  90. errors int
  91. clientId int64
  92. savedNotify chan int
  93. }
  94. var TLSconfig *tls.Config
  95. var max_size int // max email DATA size
  96. var timeout time.Duration
  97. var allowedHosts = make(map[string]bool, 15)
  98. var sem chan int // currently active clients
  99. var SaveMailChan chan *Client // workers for saving mail
  100. // defaults. Overwrite any of these in the configure() function which loads them from a json file
  101. var gConfig = map[string]string{
  102. "GSMTP_MAX_SIZE": "131072",
  103. "GSMTP_HOST_NAME": "server.example.com", // This should also be set to reflect your RDNS
  104. "GSMTP_VERBOSE": "Y",
  105. "GSMTP_LOG_FILE": "", // Eg. /var/log/goguerrilla.log or leave blank if no logging
  106. "GSMTP_TIMEOUT": "100", // how many seconds before timeout.
  107. "MYSQL_HOST": "127.0.0.1:3306",
  108. "MYSQL_USER": "gmail_mail",
  109. "MYSQL_PASS": "ok",
  110. "MYSQL_DB": "gmail_mail",
  111. "GM_MAIL_TABLE": "new_mail",
  112. "GSTMP_LISTEN_INTERFACE": "0.0.0.0:25",
  113. "GSMTP_PUB_KEY": "/etc/ssl/certs/ssl-cert-snakeoil.pem",
  114. "GSMTP_PRV_KEY": "/etc/ssl/private/ssl-cert-snakeoil.key",
  115. "GM_ALLOWED_HOSTS": "guerrillamail.de,guerrillamailblock.com",
  116. "GM_PRIMARY_MAIL_HOST": "guerrillamail.com",
  117. "GM_MAX_CLIENTS": "500",
  118. "NGINX_AUTH_ENABLED": "N", // Y or N
  119. "NGINX_AUTH": "127.0.0.1:8025", // If using Nginx proxy, ip and port to serve Auth requsts
  120. "SGID": "1008", // group id
  121. "SUID": "1008", // user id, from /etc/passwd
  122. }
  123. type redisClient struct {
  124. count int
  125. conn redis.Conn
  126. time int
  127. }
  128. func logln(level int, s string) {
  129. if gConfig["GSMTP_VERBOSE"] == "Y" {
  130. fmt.Println(s)
  131. }
  132. if level == 2 {
  133. log.Fatalf(s)
  134. }
  135. if len(gConfig["GSMTP_LOG_FILE"]) > 0 {
  136. log.Println(s)
  137. }
  138. }
  139. func configure() {
  140. var configFile, verbose, iface string
  141. log.SetOutput(os.Stdout)
  142. // parse command line arguments
  143. flag.StringVar(&configFile, "config", "goguerrilla.conf", "Path to the configuration file")
  144. flag.StringVar(&verbose, "v", "n", "Verbose, [y | n] ")
  145. flag.StringVar(&iface, "if", "", "Interface and port to listen on, eg. 127.0.0.1:2525 ")
  146. flag.Parse()
  147. // load in the config.
  148. b, err := ioutil.ReadFile(configFile)
  149. if err != nil {
  150. log.Fatalln("Could not read config file")
  151. }
  152. var myConfig map[string]string
  153. err = json.Unmarshal(b, &myConfig)
  154. if err != nil {
  155. log.Fatalln("Could not parse config file")
  156. }
  157. for k, v := range myConfig {
  158. gConfig[k] = v
  159. }
  160. gConfig["GSMTP_VERBOSE"] = strings.ToUpper(verbose)
  161. if len(iface) > 0 {
  162. gConfig["GSTMP_LISTEN_INTERFACE"] = iface
  163. }
  164. // map the allow hosts for easy lookup
  165. if arr := strings.Split(gConfig["GM_ALLOWED_HOSTS"], ","); len(arr) > 0 {
  166. for i := 0; i < len(arr); i++ {
  167. allowedHosts[arr[i]] = true
  168. }
  169. }
  170. var n int
  171. var n_err error
  172. // sem is an active clients channel used for counting clients
  173. if n, n_err = strconv.Atoi(gConfig["GM_MAX_CLIENTS"]); n_err != nil {
  174. n = 50
  175. }
  176. // currently active client list
  177. sem = make(chan int, n)
  178. // database writing workers
  179. SaveMailChan = make(chan *Client, 5)
  180. // timeout for reads
  181. if n, n_err = strconv.Atoi(gConfig["GSMTP_TIMEOUT"]); n_err != nil {
  182. timeout = time.Duration(10)
  183. } else {
  184. timeout = time.Duration(n)
  185. }
  186. // max email size
  187. if max_size, n_err = strconv.Atoi(gConfig["GSMTP_MAX_SIZE"]); n_err != nil {
  188. max_size = 131072
  189. }
  190. // custom log file
  191. if len(gConfig["GSMTP_LOG_FILE"]) > 0 {
  192. logfile, err := os.OpenFile(gConfig["GSMTP_LOG_FILE"], os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_SYNC, 0600)
  193. if err != nil {
  194. log.Fatal("Unable to open log file ["+gConfig["GSMTP_LOG_FILE"]+"]: ", err)
  195. }
  196. log.SetOutput(logfile)
  197. }
  198. return
  199. }
  200. func main() {
  201. configure()
  202. cert, err := tls.LoadX509KeyPair(gConfig["GSMTP_PUB_KEY"], gConfig["GSMTP_PRV_KEY"])
  203. if err != nil {
  204. logln(2, fmt.Sprintf("There was a problem with loading the certificate: %s", err))
  205. }
  206. TLSconfig = &tls.Config{Certificates: []tls.Certificate{cert}, ClientAuth: tls.VerifyClientCertIfGiven, ServerName: gConfig["GSMTP_HOST_NAME"]}
  207. TLSconfig.Rand = rand.Reader
  208. // start some savemail workers
  209. for i := 0; i < 3; i++ {
  210. go saveMail()
  211. }
  212. if gConfig["NGINX_AUTH_ENABLED"] == "Y" {
  213. go nginxHTTPAuth()
  214. }
  215. // Start listening for SMTP connections
  216. listener, err := net.Listen("tcp", gConfig["GSTMP_LISTEN_INTERFACE"])
  217. if err != nil {
  218. logln(2, fmt.Sprintf("Cannot listen on port, %v", err))
  219. } else {
  220. logln(1, fmt.Sprintf("Listening on tcp %s", gConfig["GSTMP_LISTEN_INTERFACE"]))
  221. }
  222. var clientId int64
  223. clientId = 1
  224. for {
  225. conn, err := listener.Accept()
  226. if err != nil {
  227. logln(1, fmt.Sprintf("Accept error: %s", err))
  228. continue
  229. }
  230. logln(1, fmt.Sprintf(" There are now "+strconv.Itoa(runtime.NumGoroutine())+" serving goroutines"))
  231. sem <- 1 // Wait for active queue to drain.
  232. go handleClient(&Client{
  233. conn: conn,
  234. address: conn.RemoteAddr().String(),
  235. time: time.Now().Unix(),
  236. bufin: bufio.NewReader(conn),
  237. bufout: bufio.NewWriter(conn),
  238. clientId: clientId,
  239. savedNotify: make(chan int),
  240. })
  241. clientId++
  242. }
  243. }
  244. func handleClient(client *Client) {
  245. defer closeClient(client)
  246. // defer closeClient(client)
  247. greeting := "220 " + gConfig["GSMTP_HOST_NAME"] +
  248. " SMTP Guerrilla-SMTPd #" + strconv.FormatInt(client.clientId, 10) + " (" + strconv.Itoa(len(sem)) + ") " + time.Now().Format(time.RFC1123Z)
  249. advertiseTls := "250-STARTTLS\r\n"
  250. for i := 0; i < 100; i++ {
  251. switch client.state {
  252. case 0:
  253. responseAdd(client, greeting)
  254. client.state = 1
  255. case 1:
  256. input, err := readSmtp(client)
  257. if err != nil {
  258. logln(1, fmt.Sprintf("Read error: %v", err))
  259. if err == io.EOF {
  260. // client closed the connection already
  261. return
  262. }
  263. if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
  264. // too slow, timeout
  265. return
  266. }
  267. break
  268. }
  269. input = strings.Trim(input, " \n\r")
  270. cmd := strings.ToUpper(input)
  271. switch {
  272. case strings.Index(cmd, "HELO") == 0:
  273. if len(input) > 5 {
  274. client.helo = input[5:]
  275. }
  276. responseAdd(client, "250 "+gConfig["GSMTP_HOST_NAME"]+" Hello ")
  277. case strings.Index(cmd, "EHLO") == 0:
  278. if len(input) > 5 {
  279. client.helo = input[5:]
  280. }
  281. 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")
  282. case strings.Index(cmd, "MAIL FROM:") == 0:
  283. if len(input) > 10 {
  284. client.mail_from = input[10:]
  285. }
  286. responseAdd(client, "250 Ok")
  287. case strings.Index(cmd, "XCLIENT") == 0:
  288. // Nginx sends this
  289. // XCLIENT ADDR=212.96.64.216 NAME=[UNAVAILABLE]
  290. client.address = input[13:]
  291. client.address = client.address[0:strings.Index(client.address, " ")]
  292. fmt.Println("client address:[" + client.address + "]")
  293. responseAdd(client, "250 OK")
  294. case strings.Index(cmd, "RCPT TO:") == 0:
  295. if len(input) > 8 {
  296. client.rcpt_to = input[8:]
  297. }
  298. responseAdd(client, "250 Accepted")
  299. case strings.Index(cmd, "NOOP") == 0:
  300. responseAdd(client, "250 OK")
  301. case strings.Index(cmd, "RSET") == 0:
  302. client.mail_from = ""
  303. client.rcpt_to = ""
  304. responseAdd(client, "250 OK")
  305. case strings.Index(cmd, "DATA") == 0:
  306. responseAdd(client, "354 Enter message, ending with \".\" on a line by itself")
  307. client.state = 2
  308. case (strings.Index(cmd, "STARTTLS") == 0) && !client.tls_on:
  309. responseAdd(client, "220 Ready to start TLS")
  310. // go to start TLS state
  311. client.state = 3
  312. case strings.Index(cmd, "QUIT") == 0:
  313. responseAdd(client, "221 Bye")
  314. killClient(client)
  315. default:
  316. responseAdd(client, fmt.Sprintf("500 unrecognized command"))
  317. client.errors++
  318. if client.errors > 3 {
  319. responseAdd(client, fmt.Sprintf("500 Too many unrecognized commands"))
  320. killClient(client)
  321. }
  322. }
  323. case 2:
  324. var err error
  325. client.data, err = readSmtp(client)
  326. if err == nil {
  327. // to do: timeout when adding to SaveMailChan
  328. // place on the channel so that one of the save mail workers can pick it up
  329. SaveMailChan <- client
  330. // wait for the save to complete
  331. status := <-client.savedNotify
  332. if status == 1 {
  333. responseAdd(client, "250 OK : queued as "+client.hash)
  334. } else {
  335. responseAdd(client, "554 Error: transaction failed, blame it on the weather")
  336. }
  337. } else {
  338. logln(1, fmt.Sprintf("DATA read error: %v", err))
  339. }
  340. client.state = 1
  341. case 3:
  342. // upgrade to TLS
  343. var tlsConn *tls.Conn
  344. tlsConn = tls.Server(client.conn, TLSconfig)
  345. err := tlsConn.Handshake() // not necessary to call here, but might as well
  346. if err == nil {
  347. client.conn = net.Conn(tlsConn)
  348. client.bufin = bufio.NewReader(client.conn)
  349. client.bufout = bufio.NewWriter(client.conn)
  350. client.tls_on = true
  351. } else {
  352. logln(1, fmt.Sprintf("Could not TLS handshake:%v", err))
  353. }
  354. advertiseTls = ""
  355. client.state = 1
  356. }
  357. // Send a response back to the client
  358. err := responseWrite(client)
  359. if err != nil {
  360. if err == io.EOF {
  361. // client closed the connection already
  362. return
  363. }
  364. if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
  365. // too slow, timeout
  366. return
  367. }
  368. }
  369. if client.kill_time > 1 {
  370. return
  371. }
  372. }
  373. }
  374. func responseAdd(client *Client, line string) {
  375. client.response = line + "\r\n"
  376. }
  377. func closeClient(client *Client) {
  378. client.conn.Close()
  379. <-sem // Done; enable next client to run.
  380. }
  381. func killClient(client *Client) {
  382. client.kill_time = time.Now().Unix()
  383. }
  384. func readSmtp(client *Client) (input string, err error) {
  385. var reply string
  386. // Command state terminator by default
  387. suffix := "\r\n"
  388. if client.state == 2 {
  389. // DATA state
  390. suffix = "\r\n.\r\n"
  391. }
  392. for err == nil {
  393. client.conn.SetDeadline(time.Now().Add(timeout * time.Second))
  394. reply, err = client.bufin.ReadString('\n')
  395. if reply != "" {
  396. input = input + reply
  397. if len(input) > max_size {
  398. err = errors.New("Maximum DATA size exceeded (" + strconv.Itoa(max_size) + ")")
  399. return input, err
  400. }
  401. if client.state == 2 {
  402. // Extract the subject while we are at it.
  403. scanSubject(client, reply)
  404. }
  405. }
  406. if err != nil {
  407. break
  408. }
  409. if strings.HasSuffix(input, suffix) {
  410. break
  411. }
  412. }
  413. return input, err
  414. }
  415. // Scan the data part for a Subject line. Can be a multi-line
  416. func scanSubject(client *Client, reply string) {
  417. if client.subject == "" && (len(reply) > 8) {
  418. test := strings.ToUpper(reply[0:9])
  419. if i := strings.Index(test, "SUBJECT: "); i == 0 {
  420. // first line with \r\n
  421. client.subject = reply[9:]
  422. }
  423. } else if strings.HasSuffix(client.subject, "\r\n") {
  424. // chop off the \r\n
  425. client.subject = client.subject[0 : len(client.subject)-2]
  426. if (strings.HasPrefix(reply, " ")) || (strings.HasPrefix(reply, "\t")) {
  427. // subject is multi-line
  428. client.subject = client.subject + reply[1:]
  429. }
  430. }
  431. }
  432. func responseWrite(client *Client) (err error) {
  433. var size int
  434. client.conn.SetDeadline(time.Now().Add(timeout * time.Second))
  435. size, err = client.bufout.WriteString(client.response)
  436. client.bufout.Flush()
  437. client.response = client.response[size:]
  438. return err
  439. }
  440. func saveMail() {
  441. var to string
  442. var err error
  443. var body string
  444. var redis_err error
  445. var length int
  446. redis := &redisClient{}
  447. db := autorc.New("tcp", "", gConfig["MYSQL_HOST"], gConfig["MYSQL_USER"], gConfig["MYSQL_PASS"], gConfig["MYSQL_DB"])
  448. db.Register("set names utf8")
  449. sql := "INSERT INTO " + gConfig["GM_MAIL_TABLE"] + " "
  450. sql += "(`date`, `to`, `from`, `subject`, `body`, `charset`, `mail`, `spam_score`, `hash`, `content_type`, `recipient`, `has_attach`, `ip_addr`)"
  451. sql += " values (NOW(), ?, ?, ?, ? , 'UTF-8' , ?, 0, ?, '', ?, 0, ?)"
  452. ins, sql_err := db.Prepare(sql)
  453. if sql_err != nil {
  454. logln(2, fmt.Sprintf("Sql statement incorrect: %s", sql_err))
  455. }
  456. sql = "UPDATE gm2_setting SET `setting_value` = `setting_value`+1 WHERE `setting_name`='received_emails' LIMIT 1"
  457. incr, sql_err := db.Prepare(sql)
  458. if sql_err != nil {
  459. logln(2, fmt.Sprintf("Sql statement incorrect: %s", sql_err))
  460. }
  461. // receives values from the channel repeatedly until it is closed.
  462. for {
  463. client := <-SaveMailChan
  464. if user, _, addr_err := validateEmailData(client); addr_err != nil { // user, host, addr_err
  465. logln(1, fmt.Sprintln("mail_from didnt validate: %v", addr_err)+" client.mail_from:"+client.mail_from)
  466. // notify client that a save completed, -1 = error
  467. client.savedNotify <- -1
  468. continue
  469. } else {
  470. to = user + "@" + gConfig["GM_PRIMARY_MAIL_HOST"]
  471. }
  472. length = len(client.data)
  473. client.subject = mimeHeaderDecode(client.subject)
  474. client.hash = md5hex(to + client.mail_from + client.subject + strconv.FormatInt(time.Now().UnixNano(), 10))
  475. // Add extra headers
  476. add_head := ""
  477. add_head += "Delivered-To: " + to + "\r\n"
  478. add_head += "Received: from " + client.helo + " (" + client.helo + " [" + client.address + "])\r\n"
  479. add_head += " by " + gConfig["GSMTP_HOST_NAME"] + " with SMTP id " + client.hash + "@" +
  480. gConfig["GSMTP_HOST_NAME"] + ";\r\n"
  481. add_head += " " + time.Now().Format(time.RFC1123Z) + "\r\n"
  482. // compress to save space
  483. client.data = compress(add_head + client.data)
  484. body = "gzencode"
  485. redis_err = redis.redisConnection()
  486. if redis_err == nil {
  487. _, do_err := redis.conn.Do("SETEX", client.hash, 3600, client.data)
  488. if do_err == nil {
  489. client.data = ""
  490. body = "redis"
  491. }
  492. } else {
  493. logln(1, fmt.Sprintf("redis: %v", redis_err))
  494. }
  495. // bind data to cursor
  496. ins.Bind(
  497. to,
  498. client.mail_from,
  499. client.subject,
  500. body,
  501. client.data,
  502. client.hash,
  503. to,
  504. client.address)
  505. // save, discard result
  506. _, _, err = ins.Exec()
  507. if err != nil {
  508. logln(1, fmt.Sprintf("Database error, %v %v", err))
  509. client.savedNotify <- -1
  510. } else {
  511. logln(1, "Email saved "+client.hash+" len:"+strconv.Itoa(length))
  512. _, _, err = incr.Exec()
  513. if err != nil {
  514. logln(1, fmt.Sprintf("Failed to incr count:", err))
  515. }
  516. client.savedNotify <- 1
  517. }
  518. }
  519. }
  520. func (c *redisClient) redisConnection() (err error) {
  521. if c.count > 100 {
  522. c.conn.Close()
  523. c.count = 0
  524. }
  525. if c.count == 0 {
  526. c.conn, err = redis.Dial("tcp", ":6379")
  527. if err != nil {
  528. // handle error
  529. return err
  530. }
  531. }
  532. return nil
  533. }
  534. func validateEmailData(client *Client) (user string, host string, addr_err error) {
  535. if user, host, addr_err = extractEmail(client.mail_from); addr_err != nil {
  536. return user, host, addr_err
  537. }
  538. client.mail_from = user + "@" + host
  539. if user, host, addr_err = extractEmail(client.rcpt_to); addr_err != nil {
  540. return user, host, addr_err
  541. }
  542. client.rcpt_to = user + "@" + host
  543. // check if on allowed hosts
  544. if allowed := allowedHosts[host]; !allowed {
  545. return user, host, errors.New("invalid host:" + host)
  546. }
  547. return user, host, addr_err
  548. }
  549. func extractEmail(str string) (name string, host string, err error) {
  550. re, _ := regexp.Compile(`<(.+?)@(.+?)>`) // go home regex, you're drunk!
  551. if matched := re.FindStringSubmatch(str); len(matched) > 2 {
  552. host = validHost(matched[2])
  553. name = matched[1]
  554. } else {
  555. if res := strings.Split(str, "@"); len(res) > 1 {
  556. name = res[0]
  557. host = validHost(res[1])
  558. }
  559. }
  560. if host == "" || name == "" {
  561. err = errors.New("Invalid address, [" + name + "@" + host + "] address:" + str)
  562. }
  563. return name, host, err
  564. }
  565. // Decode strings in Mime header format
  566. // eg. =?ISO-2022-JP?B?GyRCIVo9dztSOWJAOCVBJWMbKEI=?=
  567. func mimeHeaderDecode(str string) string {
  568. reg, _ := regexp.Compile(`=\?(.+?)\?([QBqp])\?(.+?)\?=`)
  569. matched := reg.FindAllStringSubmatch(str, -1)
  570. var charset, encoding, payload string
  571. if matched != nil {
  572. for i := 0; i < len(matched); i++ {
  573. if len(matched[i]) > 2 {
  574. charset = matched[i][1]
  575. encoding = strings.ToUpper(matched[i][2])
  576. payload = matched[i][3]
  577. switch encoding {
  578. case "B":
  579. str = strings.Replace(str, matched[i][0], mailTransportDecode(payload, "base64", charset), 1)
  580. case "Q":
  581. str = strings.Replace(str, matched[i][0], mailTransportDecode(payload, "quoted-printable", charset), 1)
  582. }
  583. }
  584. }
  585. }
  586. return str
  587. }
  588. func validHost(host string) string {
  589. host = strings.Trim(host, " ")
  590. 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])$`)
  591. if re.MatchString(host) {
  592. return host
  593. }
  594. return ""
  595. }
  596. // decode from 7bit to 8bit UTF-8
  597. // encoding_type can be "base64" or "quoted-printable"
  598. func mailTransportDecode(str string, encoding_type string, charset string) string {
  599. if charset == "" {
  600. charset = "UTF-8"
  601. } else {
  602. charset = strings.ToUpper(charset)
  603. }
  604. if encoding_type == "base64" {
  605. str = fromBase64(str)
  606. } else if encoding_type == "quoted-printable" {
  607. str = fromQuotedP(str)
  608. }
  609. if charset != "UTF-8" {
  610. charset = fixCharset(charset)
  611. // eg. charset can be "ISO-2022-JP"
  612. convstr, err := iconv.Conv(str, "UTF-8", charset)
  613. if err == nil {
  614. return convstr
  615. }
  616. }
  617. return str
  618. }
  619. func fromBase64(data string) string {
  620. buf := bytes.NewBufferString(data)
  621. decoder := base64.NewDecoder(base64.StdEncoding, buf)
  622. res, _ := ioutil.ReadAll(decoder)
  623. return string(res)
  624. }
  625. func fromQuotedP(data string) string {
  626. buf := bytes.NewBufferString(data)
  627. decoder := qprintable.NewDecoder(qprintable.BinaryEncoding, buf)
  628. res, _ := ioutil.ReadAll(decoder)
  629. return string(res)
  630. }
  631. func compress(s string) string {
  632. var b bytes.Buffer
  633. w, _ := zlib.NewWriterLevel(&b, zlib.BestSpeed) // flate.BestCompression
  634. w.Write([]byte(s))
  635. w.Close()
  636. return b.String()
  637. }
  638. func fixCharset(charset string) string {
  639. reg, _ := regexp.Compile(`[_:.\/\\]`)
  640. fixed_charset := reg.ReplaceAllString(charset, "-")
  641. // Fix charset
  642. // borrowed from http://squirrelmail.svn.sourceforge.net/viewvc/squirrelmail/trunk/squirrelmail/include/languages.php?revision=13765&view=markup
  643. // OE ks_c_5601_1987 > cp949
  644. fixed_charset = strings.Replace(fixed_charset, "ks-c-5601-1987", "cp949", -1)
  645. // Moz x-euc-tw > euc-tw
  646. fixed_charset = strings.Replace(fixed_charset, "x-euc", "euc", -1)
  647. // Moz x-windows-949 > cp949
  648. fixed_charset = strings.Replace(fixed_charset, "x-windows_", "cp", -1)
  649. // windows-125x and cp125x charsets
  650. fixed_charset = strings.Replace(fixed_charset, "windows-", "cp", -1)
  651. // ibm > cp
  652. fixed_charset = strings.Replace(fixed_charset, "ibm", "cp", -1)
  653. // iso-8859-8-i -> iso-8859-8
  654. fixed_charset = strings.Replace(fixed_charset, "iso-8859-8-i", "iso-8859-8", -1)
  655. if charset != fixed_charset {
  656. return fixed_charset
  657. }
  658. return charset
  659. }
  660. func md5hex(str string) string {
  661. h := md5.New()
  662. h.Write([]byte(str))
  663. sum := h.Sum([]byte{})
  664. return hex.EncodeToString(sum)
  665. }
  666. // If running Nginx as a proxy, give Nginx the IP address and port for the SMTP server
  667. // Primary use of Nginx is to terminate TLS so that Go doesn't need to deal with it.
  668. // This could perform auth and load balancing too
  669. // See http://wiki.nginx.org/MailCoreModule
  670. func nginxHTTPAuth() {
  671. parts := strings.Split(gConfig["GSTMP_LISTEN_INTERFACE"], ":")
  672. gConfig["HTTP_AUTH_HOST"] = parts[0]
  673. gConfig["HTTP_AUTH_PORT"] = parts[1]
  674. fmt.Println(parts)
  675. http.HandleFunc("/", nginxHTTPAuthHandler)
  676. err := http.ListenAndServe(gConfig["NGINX_AUTH"], nil)
  677. if err != nil {
  678. log.Fatal("ListenAndServe: ", err)
  679. }
  680. }
  681. func nginxHTTPAuthHandler(w http.ResponseWriter, r *http.Request) {
  682. w.Header().Add("Auth-Status", "OK")
  683. w.Header().Add("Auth-Server", gConfig["HTTP_AUTH_HOST"])
  684. w.Header().Add("Auth-Port", gConfig["HTTP_AUTH_PORT"])
  685. fmt.Fprint(w, "")
  686. }