quorate/services/oci-l4-relay-guest/smtp_deny_state.go

113 lines
3.4 KiB
Go
Raw Normal View History

package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"time"
)
const (
smtpDenyActiveStateFile = "active-state.json"
smtpDenyRefreshStateFile = "refresh-state.json"
)
type smtpDenyActiveState struct {
ActivatedAt time.Time `json:"activated_at"`
Source string `json:"source"`
BundleGeneratedAt time.Time `json:"bundle_generated_at,omitempty"`
BundleExpiresAt time.Time `json:"bundle_expires_at,omitempty"`
LastRemoteCurrentAt time.Time `json:"last_remote_current_at,omitempty"`
}
type smtpDenyRefreshState struct {
CompletedAt time.Time `json:"completed_at"`
Successful bool `json:"successful"`
ActiveSource string `json:"active_source"`
}
func readSMTPDenyActiveState(directory string) (smtpDenyActiveState, error) {
state := smtpDenyActiveState{}
if err := readStrictJSON(filepath.Join(directory, smtpDenyActiveStateFile), &state); err != nil {
return smtpDenyActiveState{}, err
}
return state, nil
}
func readSMTPDenyRefreshState(directory string) (smtpDenyRefreshState, error) {
state := smtpDenyRefreshState{}
if err := readStrictJSON(filepath.Join(directory, smtpDenyRefreshStateFile), &state); err != nil {
return smtpDenyRefreshState{}, err
}
return state, nil
}
func readStrictJSON(path string, destination any) error {
contents, err := os.ReadFile(path)
if err != nil {
return err
}
decoder := json.NewDecoder(bytes.NewReader(contents))
decoder.DisallowUnknownFields()
if err := decoder.Decode(destination); err != nil {
return err
}
if err := decoder.Decode(&struct{}{}); err != io.EOF {
return errors.New("state contains trailing data")
}
return nil
}
func (state smtpDenyActiveState) validAt(now time.Time, maximumLifetime time.Duration) bool {
if state.Source != "current" && state.Source != "cache" {
return false
}
if state.ActivatedAt.IsZero() || state.ActivatedAt.After(now.Add(5*time.Minute)) {
return false
}
if state.BundleGeneratedAt.IsZero() || state.BundleGeneratedAt.After(now.Add(5*time.Minute)) {
return false
}
if !state.BundleExpiresAt.After(now) || !state.BundleExpiresAt.After(state.BundleGeneratedAt) {
return false
}
if state.BundleExpiresAt.Sub(state.BundleGeneratedAt) > maximumLifetime {
return false
}
if !state.LastRemoteCurrentAt.IsZero() && state.LastRemoteCurrentAt.After(now.Add(5*time.Minute)) {
return false
}
if !state.LastRemoteCurrentAt.IsZero() && state.LastRemoteCurrentAt.After(state.ActivatedAt) {
return false
}
if state.Source == "current" && state.LastRemoteCurrentAt.IsZero() {
return false
}
return true
}
func completeSMTPDenyRefresh(operationErr error) error {
return completeSMTPDenyRefreshAt(smtpDenyStateDirectory, time.Now().UTC(), operationErr)
}
func completeSMTPDenyRefreshAt(directory string, completedAt time.Time, operationErr error) error {
state := smtpDenyRefreshState{
CompletedAt: completedAt,
Successful: operationErr == nil,
}
if active, err := readSMTPDenyActiveState(directory); err == nil {
state.ActiveSource = active.Source
}
contents, err := json.Marshal(state)
if err != nil {
return errors.Join(operationErr, err)
}
if err := writeAtomicOwned(filepath.Join(directory, smtpDenyRefreshStateFile), append(contents, '\n'), 0600, os.Getuid(), os.Getgid()); err != nil {
return errors.Join(operationErr, fmt.Errorf("write SMTP deny refresh state: %w", err))
}
return operationErr
}