Exemple #1
0
func (p *MtaProtocol) GetCmd() (*Cmd, error) {
	cmd, err := p.parser.ParseCommand(p.br)
	if err != nil {
		log.WithFields(log.Fields{
			"err": err,
		}).Debug("MtaProtocol.GetCmd could not parse command")
		return nil, err
	}

	log.WithFields(log.Fields{
		"Cmd":       fmt.Sprintf("%#v", cmd),
		"SessionId": p.state.SessionId.String(),
		"Ip":        p.state.Ip.String(),
	}).Debug("Received cmd")
	return &cmd, nil
}
Exemple #2
0
func (p *MtaProtocol) Send(c Cmd) {
	log.WithFields(log.Fields{
		"Cmd":       fmt.Sprintf("%#v", c),
		"SessionId": p.state.SessionId.String(),
		"Ip":        p.state.Ip.String(),
	}).Debug("Sending cmd")
	fmt.Fprintf(p.c, "%s\r\n", c)
}
Exemple #3
0
// HandleClient Start communicating with a client
func (s *Mta) HandleClient(proto smtp.Protocol) {
	//log.Printf("Received connection")

	// Hold state for this client connection
	state := proto.GetState()
	state.Reset()
	state.SessionId = generateSessionId()
	state.Ip = proto.GetIP()

	log.WithFields(log.Fields{
		"SessionId": state.SessionId.String(),
		"Ip":        state.Ip.String(),
	}).Debug("Received connection")

	if s.config.Blacklist != nil {
		if s.config.Blacklist.CheckIp(state.Ip.String()) {
			log.WithFields(log.Fields{
				"SessionId": state.SessionId.String(),
				"Ip":        state.Ip.String(),
			}).Warn("IP found in Blacklist, closing handler")
			proto.Close()
		} else {
			log.WithFields(log.Fields{
				"SessionId": state.SessionId.String(),
				"Ip":        state.Ip.String(),
			}).Debug("IP not found in Blacklist")
		}
	}

	// Start with welcome message
	proto.Send(smtp.Answer{
		Status:  smtp.Ready,
		Message: s.config.Hostname + " Service Ready",
	})

	var c *smtp.Cmd
	var err error

	quit := false
	cmdC := make(chan bool)

	nextCmd := func() bool {
		go func() {
			for {
				c, err = proto.GetCmd()

				if err != nil {
					if err == smtp.ErrLtl {
						proto.Send(smtp.Answer{
							Status:  smtp.SyntaxError,
							Message: "Line too long.",
						})
					} else {
						// Not a line too long error. What to do?
						cmdC <- true
						return
					}
				} else {
					break
				}
			}
			cmdC <- false
		}()

		select {
		case _, ok := <-s.quitC:
			if !ok {
				proto.Send(smtp.Answer{
					Status:  smtp.ShuttingDown,
					Message: "Server is going down.",
				})
				return true
			}
		case q := <-cmdC:
			return q

		}

		return false
	}

	quit = nextCmd()

	for quit == false {

		//log.Printf("Received cmd: %#v", *c)

		switch cmd := (*c).(type) {
		case smtp.HeloCmd:
			state.Hostname = cmd.Domain
			proto.Send(smtp.Answer{
				Status:  smtp.Ok,
				Message: s.config.Hostname,
			})

		case smtp.EhloCmd:
			state.Reset()
			state.Hostname = cmd.Domain

			messages := []string{s.config.Hostname, "8BITMIME"}
			if s.hasTls() && !state.Secure {
				messages = append(messages, "STARTTLS")
			}

			messages = append(messages, "OK")

			proto.Send(smtp.MultiAnswer{
				Status:   smtp.Ok,
				Messages: messages,
			})

		case smtp.QuitCmd:
			proto.Send(smtp.Answer{
				Status:  smtp.Closing,
				Message: "Bye!",
			})
			quit = true

		case smtp.MailCmd:
			if ok, reason := state.CanReceiveMail(); !ok {
				proto.Send(smtp.Answer{
					Status:  smtp.BadSequence,
					Message: reason,
				})
				break
			}

			state.From = cmd.From
			state.EightBitMIME = cmd.EightBitMIME
			message := "Sender"
			if state.EightBitMIME {
				message += " and 8BITMIME"
			}
			message += " ok"

			proto.Send(smtp.Answer{
				Status:  smtp.Ok,
				Message: message,
			})

		case smtp.RcptCmd:
			if ok, reason := state.CanReceiveRcpt(); !ok {
				proto.Send(smtp.Answer{
					Status:  smtp.BadSequence,
					Message: reason,
				})
				break
			}

			state.To = append(state.To, cmd.To)

			proto.Send(smtp.Answer{
				Status:  smtp.Ok,
				Message: "OK",
			})

		case smtp.DataCmd:
			if ok, reason := state.CanReceiveData(); !ok {
				/*
					RFC 5321 3.3

					If there was no MAIL, or no RCPT, command, or all such commands were
					rejected, the server MAY return a "command out of sequence" (503) or
					"no valid recipients" (554) reply in response to the DATA command.
					If one of those replies (or any other 5yz reply) is received, the
					client MUST NOT send the message data; more generally, message data
					MUST NOT be sent unless a 354 reply is received.
				*/
				proto.Send(smtp.Answer{
					Status:  smtp.BadSequence,
					Message: reason,
				})
				break
			}

			message := "Start"
			if state.EightBitMIME {
				message += " 8BITMIME"
			}
			message += " mail input; end with <CRLF>.<CRLF>"
			proto.Send(smtp.Answer{
				Status:  smtp.StartData,
				Message: message,
			})

		tryAgain:
			tmpData, err := ioutil.ReadAll(&cmd.R)
			state.Data = append(state.Data, tmpData...)
			if err == smtp.ErrLtl {
				proto.Send(smtp.Answer{
					// SyntaxError or 552 error? or something else?
					Status:  smtp.SyntaxError,
					Message: "Line too long",
				})
				goto tryAgain
			} else if err == smtp.ErrIncomplete {
				// I think this can only happen on a socket if it gets closed before receiving the full data.
				proto.Send(smtp.Answer{
					Status:  smtp.SyntaxError,
					Message: "Could not parse mail data",
				})
				state.Reset()
				break

			} else if err != nil {
				//panic(err)
				log.WithFields(log.Fields{
					"SessionId": state.SessionId.String(),
				}).Panic(err)
			}

			s.MailHandler.Handle(state)

			proto.Send(smtp.Answer{
				Status:  smtp.Ok,
				Message: "Mail delivered",
			})

			// Reset state after mail was handled so we can start from a clean slate.
			state.Reset()

		case smtp.RsetCmd:
			state.Reset()
			proto.Send(smtp.Answer{
				Status:  smtp.Ok,
				Message: "OK",
			})

		case smtp.StartTlsCmd:
			if !s.hasTls() {
				proto.Send(smtp.Answer{
					Status:  smtp.NotImplemented,
					Message: "STARTTLS is not implemented",
				})
				break
			}

			if state.Secure {
				proto.Send(smtp.Answer{
					Status:  smtp.NotImplemented,
					Message: "Already in TLS mode",
				})
				break
			}

			proto.Send(smtp.Answer{
				Status:  smtp.Ready,
				Message: "Ready for TLS handshake",
			})

			err := proto.StartTls(s.TlsConfig)
			if err != nil {
				log.WithFields(log.Fields{
					"Ip":        state.Ip.String(),
					"SessionId": state.SessionId.String(),
				}).Warningf("Could not enable TLS: %v", err)
				break
			}

			log.WithFields(log.Fields{
				"Ip":        state.Ip.String(),
				"SessionId": state.SessionId.String(),
			}).Debug("TLS enabled")
			state.Reset()
			state.Secure = true

		case smtp.NoopCmd:
			proto.Send(smtp.Answer{
				Status:  smtp.Ok,
				Message: "OK",
			})

		case smtp.VrfyCmd, smtp.ExpnCmd, smtp.SendCmd, smtp.SomlCmd, smtp.SamlCmd:
			proto.Send(smtp.Answer{
				Status:  smtp.NotImplemented,
				Message: "Command not implemented",
			})

		case smtp.InvalidCmd:
			// TODO: Is this correct? An InvalidCmd is a known command with
			// invalid arguments. So we should send smtp.SyntaxErrorParam?
			// Is InvalidCmd a good name for this kind of error?
			proto.Send(smtp.Answer{
				Status:  smtp.SyntaxErrorParam,
				Message: cmd.Info,
			})

		case smtp.UnknownCmd:
			proto.Send(smtp.Answer{
				Status:  smtp.SyntaxError,
				Message: "Command not recognized",
			})

		default:
			// TODO: We get here if the switch does not handle all Cmd's defined
			// in protocol.go. That means we forgot to add it here. This should ideally
			// be checked at compile time. But if we get here anyway we probably shouldn't
			// crash...
			log.Fatalf("Command not implemented: %#v", cmd)
		}

		if quit {
			break
		}

		quit = nextCmd()
	}

	proto.Close()
	log.WithFields(log.Fields{
		"SessionId": state.SessionId.String(),
		"Ip":        state.Ip.String(),
	}).Debug("Closed connection")
}