mailinabox.go raw
1 // Package mailinabox implements a DNS provider for solving the DNS-01 challenge using Mail-in-a-Box.
2 package mailinabox
3
4 import (
5 "context"
6 "errors"
7 "fmt"
8 "net/http"
9 "time"
10
11 "github.com/go-acme/lego/v4/challenge"
12 "github.com/go-acme/lego/v4/challenge/dns01"
13 "github.com/go-acme/lego/v4/platform/config/env"
14 "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug"
15 "github.com/nrdcg/mailinabox"
16 )
17
18 // Environment variables names.
19 const (
20 envNamespace = "MAILINABOX_"
21
22 EnvEmail = envNamespace + "EMAIL"
23 EnvPassword = envNamespace + "PASSWORD"
24 EnvBaseURL = envNamespace + "BASE_URL"
25
26 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
27 EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
28 EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
29 )
30
31 var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
32
33 // Config is used to configure the creation of the DNSProvider.
34 type Config struct {
35 Email string
36 Password string
37 BaseURL string
38 PropagationTimeout time.Duration
39 PollingInterval time.Duration
40 HTTPClient *http.Client
41 }
42
43 // NewDefaultConfig returns a default configuration for the DNSProvider.
44 func NewDefaultConfig() *Config {
45 return &Config{
46 PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second),
47 PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second),
48 HTTPClient: &http.Client{
49 Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
50 },
51 }
52 }
53
54 // DNSProvider implements the challenge.Provider interface.
55 type DNSProvider struct {
56 config *Config
57 client *mailinabox.Client
58 }
59
60 // NewDNSProvider returns a DNSProvider instance configured for Mail-in-a-Box.
61 // Credentials must be passed in the environment variables:
62 // MAILINABOX_EMAIL, MAILINABOX_PASSWORD, and MAILINABOX_BASE_URL.
63 func NewDNSProvider() (*DNSProvider, error) {
64 values, err := env.Get(EnvBaseURL, EnvEmail, EnvPassword)
65 if err != nil {
66 return nil, fmt.Errorf("mailinabox: %w", err)
67 }
68
69 config := NewDefaultConfig()
70 config.BaseURL = values[EnvBaseURL]
71 config.Email = values[EnvEmail]
72 config.Password = values[EnvPassword]
73
74 return NewDNSProviderConfig(config)
75 }
76
77 // NewDNSProviderConfig return a DNSProvider instance configured for deSEC.
78 func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
79 if config == nil {
80 return nil, errors.New("mailinabox: the configuration of the DNS provider is nil")
81 }
82
83 if config.Email == "" || config.Password == "" {
84 return nil, errors.New("mailinabox: incomplete credentials, missing email or password")
85 }
86
87 if config.BaseURL == "" {
88 return nil, errors.New("mailinabox: missing base URL")
89 }
90
91 if config.HTTPClient == nil {
92 config.HTTPClient = &http.Client{Timeout: 30 * time.Second}
93 }
94
95 config.HTTPClient = clientdebug.Wrap(config.HTTPClient)
96
97 client, err := mailinabox.New(config.BaseURL, config.Email, config.Password, mailinabox.WithHTTPClient(config.HTTPClient))
98 if err != nil {
99 return nil, fmt.Errorf("mailinabox: %w", err)
100 }
101
102 return &DNSProvider{config: config, client: client}, nil
103 }
104
105 // Timeout returns the timeout and interval to use when checking for DNS propagation.
106 // Adjusting here to cope with spikes in propagation times.
107 func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
108 return d.config.PropagationTimeout, d.config.PollingInterval
109 }
110
111 // Present creates a TXT record using the specified parameters.
112 func (d *DNSProvider) Present(domain, token, keyAuth string) error {
113 ctx := context.Background()
114 info := dns01.GetChallengeInfo(domain, keyAuth)
115
116 record := mailinabox.Record{
117 Name: dns01.UnFqdn(info.EffectiveFQDN),
118 Type: "TXT",
119 Value: info.Value,
120 }
121
122 _, err := d.client.DNS.AddRecord(ctx, record)
123 if err != nil {
124 return fmt.Errorf("mailinabox: add record: %w", err)
125 }
126
127 return nil
128 }
129
130 // CleanUp removes the TXT record matching the specified parameters.
131 func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
132 ctx := context.Background()
133 info := dns01.GetChallengeInfo(domain, keyAuth)
134
135 record := mailinabox.Record{
136 Name: dns01.UnFqdn(info.EffectiveFQDN),
137 Type: "TXT",
138 Value: info.Value,
139 }
140
141 _, err := d.client.DNS.RemoveRecord(ctx, record)
142 if err != nil {
143 return fmt.Errorf("mailinabox: remove record: %w", err)
144 }
145
146 return nil
147 }
148