smtpd.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. package server
  2. import (
  3. "bufio"
  4. "crypto/tls"
  5. "fmt"
  6. "io"
  7. "net"
  8. "strings"
  9. "time"
  10. log "github.com/Sirupsen/logrus"
  11. guerrilla "github.com/flashmob/go-guerrilla"
  12. "github.com/flashmob/go-guerrilla/util"
  13. )
  14. type SmtpdServer struct {
  15. mainConfig guerrilla.Config
  16. config guerrilla.ServerConfig
  17. tlsConfig *tls.Config
  18. maxSize int // max email DATA size
  19. timeout time.Duration
  20. sem chan int // currently active client list
  21. }
  22. // Upgrades the connection to TLS
  23. // Sets up buffers with the upgraded connection
  24. func (server *SmtpdServer) upgradeToTls(client *guerrilla.Client) bool {
  25. var tlsConn *tls.Conn
  26. tlsConn = tls.Server(client.Conn, server.tlsConfig)
  27. err := tlsConn.Handshake()
  28. if err == nil {
  29. client.Conn = net.Conn(tlsConn)
  30. client.Bufin = guerrilla.NewSMTPBufferedReader(client.Conn)
  31. client.Bufout = bufio.NewWriter(client.Conn)
  32. client.TLS = true
  33. return true
  34. }
  35. log.WithError(err).Warn("Failed to TLS handshake")
  36. return false
  37. }
  38. func (server *SmtpdServer) handleClient(client *guerrilla.Client, backend guerrilla.Backend) {
  39. defer server.closeClient(client)
  40. advertiseTLS := "250-STARTTLS\r\n"
  41. if server.config.TLSAlwaysOn {
  42. if server.upgradeToTls(client) {
  43. advertiseTLS = ""
  44. }
  45. }
  46. greeting := fmt.Sprintf("220 %s SMTP guerrillad(%s) #%d (%d) %s",
  47. server.config.Hostname, guerrilla.Version, client.ClientID,
  48. len(server.sem), time.Now().Format(time.RFC1123Z))
  49. if !server.config.StartTLS {
  50. // STARTTLS turned off
  51. advertiseTLS = ""
  52. }
  53. for i := 0; i < 100; i++ {
  54. switch client.State {
  55. case 0:
  56. responseAdd(client, greeting)
  57. client.State = 1
  58. case 1:
  59. client.Bufin.SetLimit(guerrilla.CommandMaxLength)
  60. input, err := server.readSmtp(client)
  61. if err != nil {
  62. if err == io.EOF {
  63. log.WithError(err).Debugf("Client closed the connection already: %s", client.Address)
  64. return
  65. } else if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
  66. log.WithError(err).Debugf("Timeout: %s", client.Address)
  67. return
  68. } else if err == guerrilla.InputLimitExceeded {
  69. responseAdd(client, "500 Line too long.")
  70. // kill it so that another one can connect
  71. killClient(client)
  72. }
  73. log.WithError(err).Warnf("Read error: %s", client.Address)
  74. break
  75. }
  76. input = strings.Trim(input, " \n\r")
  77. bound := len(input)
  78. if bound > 16 {
  79. bound = 16
  80. }
  81. cmd := strings.ToUpper(input[0:bound])
  82. switch {
  83. case strings.Index(cmd, "HELO") == 0:
  84. if len(input) > 5 {
  85. client.Helo = input[5:]
  86. }
  87. responseAdd(client, "250 "+server.config.Hostname+" Hello ")
  88. case strings.Index(cmd, "EHLO") == 0:
  89. if len(input) > 5 {
  90. client.Helo = input[5:]
  91. }
  92. responseAdd(client, fmt.Sprintf(
  93. "250-%s Hello %s[%s]\r\n"+
  94. "250-SIZE %d\r\n" +
  95. "250-PIPELINING\r\n" +
  96. "%s250 HELP",
  97. server.config.Hostname, client.Helo, client.Address,
  98. server.config.MaxSize, advertiseTLS))
  99. case strings.Index(cmd, "HELP") == 0:
  100. responseAdd(client, "250 Help! I need somebody...")
  101. case strings.Index(cmd, "MAIL FROM:") == 0:
  102. if len(input) > 10 {
  103. client.MailFrom = input[10:]
  104. }
  105. responseAdd(client, "250 Ok")
  106. case strings.Index(cmd, "XCLIENT") == 0:
  107. // Nginx sends this
  108. // XCLIENT ADDR=212.96.64.216 NAME=[UNAVAILABLE]
  109. client.Address = input[13:]
  110. client.Address = client.Address[0:strings.Index(client.Address, " ")]
  111. fmt.Println("client address:[" + client.Address + "]")
  112. responseAdd(client, "250 OK")
  113. case strings.Index(cmd, "RCPT TO:") == 0:
  114. if len(input) > 8 {
  115. client.RcptTo = input[8:]
  116. }
  117. responseAdd(client, "250 Accepted")
  118. case strings.Index(cmd, "NOOP") == 0:
  119. responseAdd(client, "250 OK")
  120. case strings.Index(cmd, "RSET") == 0:
  121. client.MailFrom = ""
  122. client.RcptTo = ""
  123. responseAdd(client, "250 OK")
  124. case strings.Index(cmd, "DATA") == 0:
  125. responseAdd(client, "354 Enter message, ending with \".\" on a line by itself")
  126. client.State = 2
  127. case (strings.Index(cmd, "STARTTLS") == 0) &&
  128. !client.TLS &&
  129. server.config.StartTLS:
  130. responseAdd(client, "220 Ready to start TLS")
  131. // go to start TLS state
  132. client.State = 3
  133. case strings.Index(cmd, "QUIT") == 0:
  134. responseAdd(client, "221 Bye")
  135. killClient(client)
  136. default:
  137. responseAdd(client, "500 unrecognized command: "+cmd)
  138. client.Errors++
  139. if client.Errors > 3 {
  140. responseAdd(client, "500 Too many unrecognized commands")
  141. killClient(client)
  142. }
  143. }
  144. case 2:
  145. var err error
  146. client.Bufin.SetLimit(int64(server.config.MaxSize) + 1024000) // This is a hard limit.
  147. client.Data, err = server.readSmtp(client)
  148. if err == nil {
  149. from, mailErr := util.ExtractEmail(client.MailFrom)
  150. if mailErr != nil {
  151. responseAdd(client, fmt.Sprintf("550 Error: invalid from: ", mailErr.Error()))
  152. } else {
  153. // TODO: support multiple RcptTo
  154. to, mailErr := util.ExtractEmail(client.RcptTo)
  155. if mailErr != nil {
  156. responseAdd(client, fmt.Sprintf("550 Error: invalid from: ", mailErr.Error()))
  157. } else {
  158. client.MailFrom = from.String()
  159. client.RcptTo = to.String()
  160. if !server.mainConfig.IsAllowed(to.Host) {
  161. responseAdd(client, "550 Error: not allowed")
  162. } else {
  163. toArray := []*guerrilla.EmailParts{to}
  164. resp := backend.Process(client, from, toArray)
  165. responseAdd(client, resp)
  166. }
  167. }
  168. }
  169. } else {
  170. if err == guerrilla.InputLimitExceeded {
  171. // hard limit reached, end to make room for other clients
  172. responseAdd(client, "550 Error: DATA limit exceeded by more than a megabyte!")
  173. killClient(client)
  174. } else {
  175. responseAdd(client, "550 Error: "+err.Error())
  176. }
  177. log.WithError(err).Warn("DATA read error")
  178. }
  179. client.State = 1
  180. case 3:
  181. // upgrade to TLS
  182. if server.upgradeToTls(client) {
  183. advertiseTLS = ""
  184. client.State = 1
  185. }
  186. }
  187. // Send a response back to the client
  188. err := server.responseWrite(client)
  189. if err != nil {
  190. if err == io.EOF {
  191. // client closed the connection already
  192. return
  193. }
  194. if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
  195. // too slow, timeout
  196. return
  197. }
  198. }
  199. if client.KillTime > 1 {
  200. return
  201. }
  202. }
  203. }
  204. // add a response on the response buffer
  205. func responseAdd(client *guerrilla.Client, line string) {
  206. client.Response = line + "\r\n"
  207. }
  208. func (server *SmtpdServer) closeClient(client *guerrilla.Client) {
  209. client.Conn.Close()
  210. <-server.sem // Done; enable next client to run.
  211. }
  212. func killClient(client *guerrilla.Client) {
  213. client.KillTime = time.Now().Unix()
  214. }
  215. // Reads from the smtpBufferedReader, can be in command state or data state.
  216. func (server *SmtpdServer) readSmtp(client *guerrilla.Client) (input string, err error) {
  217. var reply string
  218. // Command state terminator by default
  219. suffix := "\r\n"
  220. if client.State == 2 {
  221. // DATA state ends with a dot on a line by itself
  222. suffix = "\r\n.\r\n"
  223. }
  224. for err == nil {
  225. client.Conn.SetDeadline(time.Now().Add(server.timeout * time.Second))
  226. reply, err = client.Bufin.ReadString('\n')
  227. if reply != "" {
  228. input = input + reply
  229. if len(input) > server.config.MaxSize {
  230. err = fmt.Errorf("Maximum DATA size exceeded (%d)", server.config.MaxSize)
  231. return input, err
  232. }
  233. if client.State == 2 {
  234. // Extract the subject while we are at it.
  235. scanSubject(client, reply)
  236. }
  237. }
  238. if err != nil {
  239. break
  240. }
  241. if strings.HasSuffix(input, suffix) {
  242. // discard the suffix and stop reading
  243. input = input[0:len(input)-len(suffix)]
  244. break
  245. }
  246. }
  247. return input, err
  248. }
  249. // Scan the data part for a Subject line. Can be a multi-line
  250. func scanSubject(client *guerrilla.Client, reply string) {
  251. if client.Subject == "" && (len(reply) > 8) {
  252. test := strings.ToUpper(reply[0:9])
  253. if i := strings.Index(test, "SUBJECT: "); i == 0 {
  254. // first line with \r\n
  255. client.Subject = reply[9:]
  256. }
  257. } else if strings.HasSuffix(client.Subject, "\r\n") {
  258. // chop off the \r\n
  259. client.Subject = client.Subject[0 : len(client.Subject)-2]
  260. if (strings.HasPrefix(reply, " ")) || (strings.HasPrefix(reply, "\t")) {
  261. // subject is multi-line
  262. client.Subject = client.Subject + reply[1:]
  263. }
  264. }
  265. }
  266. func (server *SmtpdServer) responseWrite(client *guerrilla.Client) (err error) {
  267. var size int
  268. client.Conn.SetDeadline(time.Now().Add(server.timeout * time.Second))
  269. size, err = client.Bufout.WriteString(client.Response)
  270. client.Bufout.Flush()
  271. client.Response = client.Response[size:]
  272. return err
  273. }