// QueueAddMessage add a new mail in queue func QueueAddMessage(rawMess *[]byte, envelope message.Envelope, authUser string) (uuid string, err error) { qStore, err := NewStore(Cfg.GetStoreDriver(), Cfg.GetStoreSource()) if err != nil { return } uuid, err = NewUUID() if err != nil { return } err = qStore.Put(uuid, bytes.NewReader(*rawMess)) if err != nil { return } messageId := message.RawGetMessageId(rawMess) cloop := 0 qmessages := []QMessage{} for _, rcptTo := range envelope.RcptTo { qm := QMessage{ Uuid: uuid, AuthUser: authUser, MailFrom: envelope.MailFrom, RcptTo: rcptTo, MessageId: string(messageId), Host: message.GetHostFromAddress(rcptTo), LastUpdate: time.Now(), AddedAt: time.Now(), NextDeliveryScheduledAt: time.Now(), Status: 2, DeliveryFailedCount: 0, } // create record in db err = DB.Create(&qm).Error if err != nil { if cloop == 0 { qStore.Del(uuid) } return } cloop++ qmessages = append(qmessages, qm) } // publish qmessage // TODO: to avoid the copy of the Lock -> qmsg.Publish() for _, qmsg := range qmessages { var jMsg []byte jMsg, err = json.Marshal(qmsg) if err != nil { if cloop == 1 { qStore.Del(uuid) } DB.Delete(&qmsg) return } // queue local | queue remote err = NsqQueueProducer.Publish("todeliver", jMsg) if err != nil { if cloop == 1 { qStore.Del(uuid) } DB.Delete(&qmsg) return } } return }
// DATA // plutot que de stocker en RAM on pourrait envoyer directement les danat // dans un fichier ne queue // Si il y a une erreur on supprime le fichier // Voir un truc comme DATA -> temp file -> mv queue file func (s *SMTPServerSession) smtpData(msg []string) { defer s.recoverOnPanic() if !s.seenMail || len(s.envelope.RcptTo) == 0 { s.log("DATA - out of sequence") s.pause(2) s.out("503 5.5.1 command out of sequence") s.SMTPResponseCode = 503 return } if len(msg) > 1 { s.log("DATA - invalid syntax: " + strings.Join(msg, " ")) s.pause(2) s.out("501 5.5.4 invalid syntax") s.SMTPResponseCode = 551 return } s.out("354 End data with <CR><LF>.<CR><LF>") s.SMTPResponseCode = 354 // Get RAW mail var rawMessage []byte ch := make([]byte, 1) //state := 0 pos := 0 // position in current line hops := 0 // nb of relay s.dataBytes = 0 // nb of bytes (size of message) flagInHeader := true flagLineMightMatchReceived := true flagLineMightMatchDelivered := true flagLineMightMatchCRLF := true state := 1 doLoop := true for { if !doLoop { break } s.resetTimeout() _, err := s.conn.Read(ch) s.timer.Stop() if err != nil { // we will tryc to send an error message to client, but there is a LOT of // chance that is gone s.logError("DATA - unable to read byte from conn. " + err.Error()) s.out("454 something wrong append will reading data from you") s.SMTPResponseCode = 454 s.exitAsap() return } if flagInHeader { // Check hops if pos < 9 { if ch[0] != byte("delivered"[pos]) && ch[0] != byte("DELIVERED"[pos]) { flagLineMightMatchDelivered = false } if flagLineMightMatchDelivered && pos == 8 { hops++ } if pos < 8 { if ch[0] != byte("received"[pos]) && ch[0] != byte("RECEIVED"[pos]) { flagLineMightMatchReceived = false } } if flagLineMightMatchReceived && pos == 7 { hops++ } if pos < 2 && ch[0] != "\r\n"[pos] { flagLineMightMatchCRLF = false } if (flagLineMightMatchCRLF) && pos == 1 { flagInHeader = false } } pos++ if ch[0] == LF { pos = 0 flagLineMightMatchCRLF = true flagLineMightMatchDelivered = true flagLineMightMatchReceived = true } } switch state { case 0: if ch[0] == LF { s.strayNewline() return } if ch[0] == CR { state = 4 rawMessage = append(rawMessage, ch[0]) s.dataBytes++ continue } // \r\n case 1: if ch[0] == LF { s.strayNewline() return } // "." if ch[0] == 46 { state = 2 continue } // "\r" if ch[0] == CR { state = 4 rawMessage = append(rawMessage, ch[0]) s.dataBytes++ continue } state = 0 // "\r\n +." case 2: if ch[0] == LF { s.strayNewline() return } if ch[0] == CR { state = 3 rawMessage = append(rawMessage, ch[0]) s.dataBytes++ continue } state = 0 //\r\n +.\r case 3: if ch[0] == LF { doLoop = false rawMessage = append(rawMessage, ch[0]) s.dataBytes++ continue } if ch[0] == CR { state = 4 rawMessage = append(rawMessage, ch[0]) s.dataBytes++ continue } state = 0 // /* + \r */ case 4: if ch[0] == LF { state = 1 break } if ch[0] != CR { rawMessage = append(rawMessage, 10) state = 0 } } rawMessage = append(rawMessage, ch[0]) s.dataBytes++ // Max hops reached ? if hops > Cfg.GetSmtpdMaxHops() { s.log(fmt.Sprintf("MAIL - Message is looping. Hops : %d", hops)) s.out("554 5.4.6 too many hops, this message is looping") s.SMTPResponseCode = 554 s.purgeConn() s.reset() return } // Max databytes reached ? if s.dataBytes > uint32(Cfg.GetSmtpdMaxDataBytes()) { s.log(fmt.Sprintf("MAIL - Message size (%d) exceeds maxDataBytes (%d).", s.dataBytes, Cfg.GetSmtpdMaxDataBytes())) s.out("552 5.3.4 sorry, that message size exceeds my databytes limit") s.SMTPResponseCode = 552 s.purgeConn() s.reset() return } } // scan // clamav if Cfg.GetSmtpdClamavEnabled() { found, virusName, err := NewClamav().ScanStream(bytes.NewReader(rawMessage)) Log.Debug("clamav scan result", found, virusName, err) if err != nil { s.logError("MAIL - clamav: " + err.Error()) s.out("454 4.3.0 scanner failure") s.SMTPResponseCode = 454 //s.purgeConn() s.reset() return } if found { s.out("554 5.7.1 message infected by " + virusName) s.SMTPResponseCode = 554 s.log("MAIL - infected by " + virusName) //s.purgeConn() s.reset() return } } // Message-ID HeaderMessageID := message.RawGetMessageId(&rawMessage) if len(HeaderMessageID) == 0 { atDomain := Cfg.GetMe() if strings.Count(s.envelope.MailFrom, "@") != 0 { atDomain = strings.ToLower(strings.Split(s.envelope.MailFrom, "@")[1]) } HeaderMessageID = []byte(fmt.Sprintf("%d.%s@%s", time.Now().Unix(), s.uuid, atDomain)) rawMessage = append([]byte(fmt.Sprintf("Message-ID: <%s>\r\n", HeaderMessageID)), rawMessage...) } s.log("message-id:", string(HeaderMessageID)) // Microservice stop, extraHeader := smtpdData(s, &rawMessage) if stop { return } for _, header2add := range *extraHeader { h := []byte(header2add) message.FoldHeader(&h) rawMessage = append([]byte(fmt.Sprintf("%s\r\n", h)), rawMessage...) } // Add recieved header remoteIP := strings.Split(s.conn.RemoteAddr().String(), ":")[0] remoteHost := "no reverse" remoteHosts, err := net.LookupAddr(remoteIP) if err == nil { remoteHost = remoteHosts[0] } localIP := strings.Split(s.conn.LocalAddr().String(), ":")[0] localHost := "no reverse" localHosts, err := net.LookupAddr(localIP) if err == nil { localHost = localHosts[0] } recieved := fmt.Sprintf("Received: from %s (%s)", remoteIP, remoteHost) // helo if len(s.helo) != 0 { recieved += fmt.Sprintf(" (%s)", s.helo) } // Authentified if s.user != nil { recieved += fmt.Sprintf(" (authenticated as %s)", s.user.Login) } // local recieved += fmt.Sprintf(" by %s (%s)", localIP, localHost) // Proto if s.tls { recieved += " with ESMTPS " + tlsGetVersion(s.connTLS.ConnectionState().Version) + " " + tlsGetCipherSuite(s.connTLS.ConnectionState().CipherSuite) + "; " } else { recieved += " whith SMTP; " } // tmail recieved += "tmail " + Version recieved += "; " + s.uuid // timestamp recieved += "; " + time.Now().Format(Time822) h := []byte(recieved) message.FoldHeader(&h) h = append(h, []byte{13, 10}...) rawMessage = append(h, rawMessage...) recieved = "" rawMessage = append([]byte("X-Env-From: "+s.envelope.MailFrom+"\r\n"), rawMessage...) // put message in queue authUser := "" if s.user != nil { authUser = s.user.Login } // microservice SmtpdBeforeQueueing if stop := msSmtpdBeforeQueueing(s); stop { return } id, err := QueueAddMessage(&rawMessage, s.envelope, authUser) if err != nil { s.logError("MAIL - unable to put message in queue -", err.Error()) s.out("451 temporary queue error") s.SMTPResponseCode = 451 s.reset() return } s.log("message queued as", id) s.out(fmt.Sprintf("250 2.0.0 Ok: queued %s", id)) s.SMTPResponseCode = 250 s.reset() return }