/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package module

import (
	"crypto/rand"
	"crypto/tls"
	"encoding/hex"
	"io"
	"net"

	"github.com/emersion/go-smtp"
	"github.com/foxcpp/maddy/framework/future"
)

// ConnState structure holds the state information of the protocol used to
// accept this message.
type ConnState struct {
	// IANA name (ESMTP, ESMTPS, etc) of the protocol message was received
	// over. If the message was generated locally, this field is empty.
	Proto string

	// Information about the SMTP connection, including HELO hostname and
	// source IP. Valid only if Proto refers the SMTP protocol or its variant
	// (e.g. LMTP).
	Hostname   string
	LocalAddr  net.Addr
	RemoteAddr net.Addr
	TLS        tls.ConnectionState

	// The RDNSName field contains the result of Reverse DNS lookup on the
	// client IP.
	//
	// The underlying type is the string or untyped nil value. It is the
	// message source responsibility to populate this field.
	//
	// Valid values of this field consumers need to be aware of:
	// RDNSName = nil
	//   The reverse DNS lookup is not applicable for that message source.
	//   Typically the case for messages generated locally.
	// RDNSName != nil, but Get returns nil
	//   The reverse DNS lookup was attempted, but resulted in an error.
	//   Consumers should assume that the PTR record doesn't exist.
	RDNSName *future.Future

	// If the client successfully authenticated using a username/password pair.
	// This field contains the username.
	AuthUser string

	// If the client successfully authenticated using a username/password pair.
	// This field should be cleaned if the ConnState object is serialized
	AuthPassword string

	ModData ModSpecificData
}

// MsgMetadata structure contains all information about the origin of
// the message and all associated flags indicating how it should be handled
// by components.
//
// All fields should be considered read-only except when otherwise is noted.
// Module instances should avoid keeping reference to the instance passed to it
// and copy the structure using DeepCopy method instead.
//
// Compatibility with older values should be considered when changing this
// structure since it is serialized to the disk by the queue module using
// JSON. Modules should correctly handle missing or invalid values.
type MsgMetadata struct {
	// Unique identifier for this message. Randomly generated by the
	// message source module.
	ID string

	// Original message sender address as it was received by the message source.
	//
	// Note that this field is meant for use for tracing purposes.
	// All routing and other decisions should be made based on the sender address
	// passed separately (for example, mailFrom argument for CheckSender function)
	// Note that addresses may contain unescaped Unicode characters.
	OriginalFrom string

	// If set - no SrcHostname and SrcAddr will be added to Received
	// header. These fields are still written to the server log.
	DontTraceSender bool

	// Quarantine is a message flag that is should be set if message is
	// considered "suspicious" and should be put into "Junk" folder
	// in the storage.
	//
	// This field should not be modified by the checks that verify
	// the message. It is set only by the message pipeline.
	Quarantine bool

	// OriginalRcpts contains the mapping from the final recipient to the
	// recipient that was presented by the client.
	//
	// MsgPipeline will update that field when recipient modifiers
	// are executed.
	//
	// It should be used when reporting information back to client (via DSN,
	// for example) to prevent disclosing information about aliases
	// which is usually unwanted.
	OriginalRcpts map[string]string

	// SMTPOpts contains the SMTP MAIL FROM command arguments, if the message
	// was accepted over SMTP or SMTP-like protocol (such as LMTP).
	//
	// Note that the Size field should not be used as source of information about
	// the body size. Especially since it counts the header too whereas
	// Buffer.Len does not.
	SMTPOpts smtp.MailOptions

	// Conn contains the information about the underlying protocol connection
	// that was used to accept this message. The referenced instance may be shared
	// between multiple messages.
	//
	// It can be nil for locally generated messages.
	Conn *ConnState

	// This is set by endpoint/smtp to indicate that body contains "TLS-Required: No"
	// header. It is only meaningful if server has seen the body at least once
	// (e.g. the message was passed via queue).
	TLSRequireOverride bool
}

// DeepCopy creates a copy of the MsgMetadata structure, also
// copying contents of the maps and slices.
//
// There are a few exceptions, however:
// - SrcAddr is not copied and copy field references original value.
func (msgMeta *MsgMetadata) DeepCopy() *MsgMetadata {
	cpy := *msgMeta
	// There is no good way to copy net.Addr, but it should not be
	// modified by anything anyway so we are safe.
	return &cpy
}

// GenerateMsgID generates a string usable as MsgID field in module.MsgMeta.
func GenerateMsgID() (string, error) {
	rawID := make([]byte, 4)
	_, err := io.ReadFull(rand.Reader, rawID)
	return hex.EncodeToString(rawID), err
}
