1
0

goguerrilla.go 20 KB

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