Example #1
0
// 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
}
Example #2
0
// 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
}