mirror of
https://github.com/foxcpp/maddy.git
synced 2025-04-06 06:27:38 +03:00
Merge branch 'dev'
This commit is contained in:
commit
61e6e73910
112 changed files with 4630 additions and 449 deletions
|
@ -13,7 +13,7 @@ nav:
|
|||
- tutorials/setting-up.md
|
||||
- tutorials/building-from-source.md
|
||||
- tutorials/alias-to-remote.md
|
||||
- tutorials/multiple-domains.md
|
||||
- tutorials/pam.md
|
||||
- Integration with software:
|
||||
- third-party/dovecot.md
|
||||
- third-party/smtp-servers.md
|
||||
|
@ -21,6 +21,7 @@ nav:
|
|||
- third-party/mailman3.md
|
||||
- seclevels.md
|
||||
- faq.md
|
||||
- multiple-domains.md
|
||||
- unicode.md
|
||||
- upgrading.md
|
||||
- specifications.md
|
||||
|
@ -29,6 +30,7 @@ nav:
|
|||
- man/_generated_maddy.1.md
|
||||
- man/_generated_maddy.5.md
|
||||
- man/_generated_maddy-auth.5.md
|
||||
- man/_generated_maddy-blob.5.md
|
||||
- man/_generated_maddy-config.5.md
|
||||
- man/_generated_maddy-filters.5.md
|
||||
- man/_generated_maddy-imap.5.md
|
||||
|
|
|
@ -722,6 +722,7 @@ func getCfgBlockModule(ctx *cli.Context) (map[string]interface{}, *maddy.ModInfo
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
module.NoRun = true
|
||||
_, mods, err := maddy.RegisterModules(globals, cfgNodes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
6
contrib/README.md
Normal file
6
contrib/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Community contributed resources
|
||||
|
||||
Disclaimer: Nothing inside subdirectories here is directly supported by Maddy
|
||||
Mail Server maintainers. Some community members may be able to help you or not.
|
||||
|
||||
- Kubernetes helm chart is maintained by @acim.
|
23
contrib/kubernetes/chart/.helmignore
Normal file
23
contrib/kubernetes/chart/.helmignore
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
23
contrib/kubernetes/chart/Chart.yaml
Normal file
23
contrib/kubernetes/chart/Chart.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
apiVersion: v2
|
||||
name: maddy
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.2.6
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: 0.4.0
|
69
contrib/kubernetes/chart/README.md
Normal file
69
contrib/kubernetes/chart/README.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# maddy Helm chart for Kubernetes
|
||||
|
||||
  
|
||||
|
||||
This is just initial effort to run maddy within Kubernetes cluster. We have used Deployment resource which has some downsides
|
||||
but at least this chart will allow you to install maddy relatively easily on your Kubernetes cluster. We have considered
|
||||
StatefulSet and DaemonSet but such solutions would require much more configuration and in casae of DaemonSet also a TCP
|
||||
load balancer in front of the nodes.
|
||||
|
||||
## Requirement
|
||||
|
||||
In order to run maddy properly, you need to have TLS secret undet name maddy present in the cluster. If you have commercial
|
||||
certificate, you can create it by the following command:
|
||||
|
||||
```sh
|
||||
kubectl create secret tls maddy --cert=fullchain.pem --key=privkey.pem
|
||||
```
|
||||
|
||||
If you use cert-manager, just create the secret under name maddy.
|
||||
|
||||
## Replication
|
||||
|
||||
Default for this chart is 1 replica of maddy. If you try to increse this, you will probably get an error because of
|
||||
the busy ports 25, 143, 587, etc. We do not support this feature at the moment, so please use just 1 replica. Like said
|
||||
at the begining of this document, multiple replicas would probably require to switch do DaemonSet which would further require
|
||||
to have TCP load balancer and shared storage between all replicas. This is not supported by this chart, sorry.
|
||||
This chart is used on one node cluster and then installation is straight forward, like described bellow, but if you have
|
||||
multiple node cluster, please use taints and tolerations to select the desired node. This chart supports tolerations to
|
||||
be set.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| -------------------------- | ------ | ----------------- | ----------- |
|
||||
| affinity | object | `{}` | |
|
||||
| fullnameOverride | string | `""` | |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | |
|
||||
| image.repository | string | `"foxcpp/maddy"` | |
|
||||
| image.tag | string | `""` | |
|
||||
| imagePullSecrets | list | `[]` | |
|
||||
| nameOverride | string | `""` | |
|
||||
| nodeSelector | object | `{}` | |
|
||||
| persistence.accessMode | string | `"ReadWriteOnce"` | |
|
||||
| persistence.annotations | object | `{}` | |
|
||||
| persistence.enabled | bool | `false` | |
|
||||
| persistence.path | string | `"/data"` | |
|
||||
| persistence.size | string | `"128Mi"` | |
|
||||
| podAnnotations | object | `{}` | |
|
||||
| podSecurityContext | object | `{}` | |
|
||||
| replicaCount | int | `1` | |
|
||||
| resources | object | `{}` | |
|
||||
| securityContext | object | `{}` | |
|
||||
| service.type | string | `"NodePort"` | |
|
||||
| serviceAccount.annotations | object | `{}` | |
|
||||
| serviceAccount.create | bool | `true` | |
|
||||
| serviceAccount.name | string | `""` | |
|
||||
| tolerations | list | `[]` | |
|
||||
|
||||
## Installing the chart
|
||||
|
||||
```sh
|
||||
helm upgrade --install maddy ./chart --set service.externapIPs[0]=1.2.3.4
|
||||
```
|
||||
|
||||
1.2.3.4 is your public IP of the node.
|
||||
|
||||
## maddy configuration
|
||||
|
||||
Feel free to tweak files/maddy.conf and files/aliases according to your needs.
|
1
contrib/kubernetes/chart/files/aliases
Normal file
1
contrib/kubernetes/chart/files/aliases
Normal file
|
@ -0,0 +1 @@
|
|||
info@example.org: foxcpp@example.org
|
171
contrib/kubernetes/chart/files/maddy.conf
Normal file
171
contrib/kubernetes/chart/files/maddy.conf
Normal file
|
@ -0,0 +1,171 @@
|
|||
## maddy 0.3 - default configuration file (2020-05-31)
|
||||
# Suitable for small-scale deployments. Uses its own format for local users DB,
|
||||
# should be managed via maddyctl utility.
|
||||
#
|
||||
# See tutorials at https://foxcpp.dev/maddy for guidance on typical
|
||||
# configuration changes.
|
||||
#
|
||||
# See manual pages (also available at https://foxcpp.dev/maddy) for reference
|
||||
# documentation.
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Base variables
|
||||
|
||||
$(hostname) = mx1.example.org
|
||||
$(primary_domain) = example.org
|
||||
$(local_domains) = $(primary_domain)
|
||||
|
||||
tls file /etc/maddy/certs/fullchain.pem /etc/maddy/certs/privkey.pem
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Local storage & authentication
|
||||
|
||||
# pass_table provides local hashed passwords storage for authentication of
|
||||
# users. It can be configured to use any "table" module, in default
|
||||
# configuration a table in SQLite DB is used.
|
||||
# Table can be replaced to use e.g. a file for passwords. Or pass_table module
|
||||
# can be replaced altogether to use some external source of credentials (e.g.
|
||||
# PAM, /etc/shadow file).
|
||||
#
|
||||
# If table module supports it (sql_table does) - credentials can be managed
|
||||
# using 'maddyctl creds' command.
|
||||
|
||||
auth.pass_table local_authdb {
|
||||
table sql_table {
|
||||
driver sqlite3
|
||||
dsn credentials.db
|
||||
table_name passwords
|
||||
}
|
||||
}
|
||||
|
||||
# imapsql module stores all indexes and metadata necessary for IMAP using a
|
||||
# relational database. It is used by IMAP endpoint for mailbox access and
|
||||
# also by SMTP & Submission endpoints for delivery of local messages.
|
||||
#
|
||||
# IMAP accounts, mailboxes and all message metadata can be inspected using
|
||||
# imap-* subcommands of maddyctl utility.
|
||||
|
||||
storage.imapsql local_mailboxes {
|
||||
driver sqlite3
|
||||
dsn imapsql.db
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# SMTP endpoints + message routing
|
||||
|
||||
hostname $(hostname)
|
||||
|
||||
msgpipeline local_routing {
|
||||
dmarc yes
|
||||
check {
|
||||
require_matching_ehlo
|
||||
require_mx_record
|
||||
dkim
|
||||
spf
|
||||
}
|
||||
|
||||
# Insert handling for special-purpose local domains here.
|
||||
# e.g.
|
||||
# destination lists.example.org {
|
||||
# deliver_to lmtp tcp://127.0.0.1:8024
|
||||
# }
|
||||
|
||||
destination postmaster $(local_domains) {
|
||||
modify {
|
||||
replace_rcpt regexp "(.+)\+(.+)@(.+)" "$1@$3"
|
||||
replace_rcpt file /data/aliases
|
||||
}
|
||||
|
||||
deliver_to &local_mailboxes
|
||||
}
|
||||
|
||||
default_destination {
|
||||
reject 550 5.1.1 "User doesn't exist"
|
||||
}
|
||||
}
|
||||
|
||||
smtp tcp://0.0.0.0:25 {
|
||||
limits {
|
||||
# Up to 20 msgs/sec across max. 10 SMTP connections.
|
||||
all rate 20 1s
|
||||
all concurrency 10
|
||||
}
|
||||
|
||||
source $(local_domains) {
|
||||
reject 501 5.1.8 "Use Submission for outgoing SMTP"
|
||||
}
|
||||
default_source {
|
||||
destination postmaster $(local_domains) {
|
||||
deliver_to &local_routing
|
||||
}
|
||||
default_destination {
|
||||
reject 550 5.1.1 "User doesn't exist"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
|
||||
limits {
|
||||
# Up to 50 msgs/sec across any amount of SMTP connections.
|
||||
all rate 50 1s
|
||||
}
|
||||
|
||||
auth &local_authdb
|
||||
|
||||
source $(local_domains) {
|
||||
destination postmaster $(local_domains) {
|
||||
deliver_to &local_routing
|
||||
}
|
||||
default_destination {
|
||||
modify {
|
||||
dkim $(primary_domain) $(local_domains) default
|
||||
}
|
||||
deliver_to &remote_queue
|
||||
}
|
||||
}
|
||||
default_source {
|
||||
reject 501 5.1.8 "Non-local sender domain"
|
||||
}
|
||||
}
|
||||
|
||||
target.remote outbound_delivery {
|
||||
limits {
|
||||
# Up to 20 msgs/sec across max. 10 SMTP connections
|
||||
# for each recipient domain.
|
||||
destination rate 20 1s
|
||||
destination concurrency 10
|
||||
}
|
||||
mx_auth {
|
||||
dane
|
||||
mtasts {
|
||||
cache fs
|
||||
fs_dir mtasts_cache/
|
||||
}
|
||||
local_policy {
|
||||
min_tls_level encrypted
|
||||
min_mx_level none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
target.queue remote_queue {
|
||||
target &outbound_delivery
|
||||
|
||||
autogenerated_msg_domain $(primary_domain)
|
||||
bounce {
|
||||
destination postmaster $(local_domains) {
|
||||
deliver_to &local_routing
|
||||
}
|
||||
default_destination {
|
||||
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# IMAP endpoints
|
||||
|
||||
imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
|
||||
auth &local_authdb
|
||||
storage &local_mailboxes
|
||||
}
|
0
contrib/kubernetes/chart/templates/NOTES.txt
Normal file
0
contrib/kubernetes/chart/templates/NOTES.txt
Normal file
62
contrib/kubernetes/chart/templates/_helpers.tpl
Normal file
62
contrib/kubernetes/chart/templates/_helpers.tpl
Normal file
|
@ -0,0 +1,62 @@
|
|||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "maddy.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "maddy.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "maddy.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "maddy.labels" -}}
|
||||
helm.sh/chart: {{ include "maddy.chart" . }}
|
||||
{{ include "maddy.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "maddy.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "maddy.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "maddy.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "maddy.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
10
contrib/kubernetes/chart/templates/configmap.yaml
Normal file
10
contrib/kubernetes/chart/templates/configmap.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{include "maddy.fullname" .}}
|
||||
labels: {{- include "maddy.labels" . | nindent 4}}
|
||||
data:
|
||||
maddy.conf: |
|
||||
{{ tpl (.Files.Get "files/maddy.conf") . | printf "%s" | indent 4 }}
|
||||
aliases: |
|
||||
{{ tpl (.Files.Get "files/aliases") . | printf "%s" | indent 4 }}
|
113
contrib/kubernetes/chart/templates/deployment.yaml
Normal file
113
contrib/kubernetes/chart/templates/deployment.yaml
Normal file
|
@ -0,0 +1,113 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "maddy.fullname" . }}
|
||||
labels:
|
||||
{{- include "maddy.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "maddy.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ tpl (.Files.Get "files/maddy.conf") . | printf "%s" | sha256sum }}
|
||||
checksum/aliases: {{ tpl (.Files.Get "files/aliases") . | printf "%s" | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "maddy.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "maddy.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
initContainers:
|
||||
- name: init
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: busybox
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- cp /tmp/maddy/* /data/.
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: {{ .Values.persistence.path }}
|
||||
{{- if .Values.persistence.subPath }}
|
||||
subPath: {{ .Values.persistence.subPath }}
|
||||
{{- end }}
|
||||
- name: config
|
||||
mountPath: /tmp/maddy
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: smtp
|
||||
containerPort: 25
|
||||
protocol: TCP
|
||||
- name: imaps
|
||||
containerPort: 993
|
||||
protocol: TCP
|
||||
- name: starttls
|
||||
containerPort: 587
|
||||
protocol: TCP
|
||||
# livenessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
# readinessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: {{ .Values.persistence.path }}
|
||||
{{- if .Values.persistence.subPath }}
|
||||
subPath: {{ .Values.persistence.subPath }}
|
||||
{{- end }}
|
||||
- name: tls
|
||||
mountPath: /etc/maddy/certs/fullchain.pem
|
||||
subPath: tls.crt
|
||||
- name: tls
|
||||
mountPath: /etc/maddy/certs/privkey.pem
|
||||
subPath: tls.key
|
||||
volumes:
|
||||
- name: data
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (include "maddy.fullname" .) .Values.persistence.existingClaim }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: config
|
||||
configMap:
|
||||
name: {{include "maddy.fullname" .}}
|
||||
- name: tls
|
||||
secret:
|
||||
secretName: {{include "maddy.fullname" .}}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
22
contrib/kubernetes/chart/templates/pvc.yaml
Normal file
22
contrib/kubernetes/chart/templates/pvc.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "maddy.fullname" . }}
|
||||
annotations:
|
||||
{{- with .Values.persistence.annotations }}
|
||||
{{ toYaml . | indent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "maddy.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
{{- if .Values.persistence.storageClass }}
|
||||
storageClassName: {{ .Values.persistence.storageClass }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
27
contrib/kubernetes/chart/templates/service.yaml
Normal file
27
contrib/kubernetes/chart/templates/service.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "maddy.fullname" . }}
|
||||
labels:
|
||||
{{- include "maddy.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: 25
|
||||
targetPort: smtp
|
||||
protocol: TCP
|
||||
name: smtp
|
||||
- port: 993
|
||||
targetPort: imaps
|
||||
protocol: TCP
|
||||
name: imaps
|
||||
- port: 587
|
||||
targetPort: starttls
|
||||
protocol: TCP
|
||||
name: starttls
|
||||
selector:
|
||||
{{- include "maddy.selectorLabels" . | nindent 4 }}
|
||||
{{- with .Values.service.externalIPs }}
|
||||
externalIPs:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end -}}
|
12
contrib/kubernetes/chart/templates/serviceaccount.yaml
Normal file
12
contrib/kubernetes/chart/templates/serviceaccount.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "maddy.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "maddy.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "maddy.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "maddy.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "maddy.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
74
contrib/kubernetes/chart/values.yaml
Normal file
74
contrib/kubernetes/chart/values.yaml
Normal file
|
@ -0,0 +1,74 @@
|
|||
# Default values for maddy.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1 # Multiple replicas are not supported, please don't change this.
|
||||
|
||||
image:
|
||||
repository: foxcpp/maddy
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext:
|
||||
{}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext:
|
||||
{}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
# Set externalPIs to your public IP(s) of the node running maddy. In case of multiple nodes, you need to set tolerations
|
||||
# and taints in order to run maddy on the exact node.
|
||||
service:
|
||||
type: NodePort
|
||||
# externalIPs:
|
||||
|
||||
resources:
|
||||
{}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
persistence:
|
||||
enabled: false
|
||||
# existingClaim: ""
|
||||
accessMode: ReadWriteOnce
|
||||
size: 128Mi
|
||||
# storageClass: ""
|
||||
path: /data
|
||||
annotations: {}
|
||||
# subPath: "" # only mount a subpath of the Volume into the pod
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
|
@ -257,3 +257,117 @@ format.
|
|||
|
||||
tcp://10.0.0.1:2222 for TCP, unix:///var/lib/dovecot/auth.sock for Unix
|
||||
domain sockets.
|
||||
|
||||
# LDAP BindDN authentication (EXPERIMENTAL) (auth.ldap)
|
||||
|
||||
maddy supports authentication via LDAP using DN binding. Passwords are verified
|
||||
by the LDAP server.
|
||||
|
||||
maddy needs to know the DN to use for binding. It can be obtained either by
|
||||
directory search or template .
|
||||
|
||||
Note that storage backends conventionally use email addresses, if you use
|
||||
non-email identifiers as usernames then you should map them onto
|
||||
emails on delivery by using auth_map (see *maddy-storage*(5)).
|
||||
|
||||
auth.ldap also can be a used as a table module. This way you can check
|
||||
whether the account exists. It works only if DN template is not used.
|
||||
|
||||
```
|
||||
auth.ldap {
|
||||
urls ldap://maddy.test:389
|
||||
|
||||
# Specify initial bind credentials. Not required ('bind off')
|
||||
# if DN template is used.
|
||||
bind plain "cn=maddy,ou=people,dc=maddy,dc=test" "123456"
|
||||
|
||||
# Specify DN template to skip lookup.
|
||||
dn_template "cn={username},ou=people,dc=maddy,dc=test"
|
||||
|
||||
# Specify base_dn and filter to lookup DN.
|
||||
base_dn "ou=people,dc=maddy,dc=test"
|
||||
filter "(&(objectClass=posixAccount)(uid={username}))"
|
||||
|
||||
tls_client { ... }
|
||||
starttls off
|
||||
debug off
|
||||
connect_timeout 1m
|
||||
}
|
||||
```
|
||||
```
|
||||
auth.ldap ldap://maddy.test.389 {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration directives
|
||||
|
||||
*Syntax:* urls _servers..._
|
||||
|
||||
REQUIRED.
|
||||
|
||||
URLs of the directory servers to use. First available server
|
||||
is used - no load-balancing is done.
|
||||
|
||||
URLs should use 'ldap://', 'ldaps://', 'ldapi://' schemes.
|
||||
|
||||
*Syntax:* bind off ++
|
||||
bind unauth ++
|
||||
bind external ++
|
||||
bind plain _username_ _password_ ++
|
||||
*Default:* off
|
||||
|
||||
Credentials to use for initial binding. Required if DN lookup is used.
|
||||
|
||||
'unauth' performs unauthenticated bind. 'external' performs external binding
|
||||
which is useful for Unix socket connections (ldapi://) or TLS client certificate
|
||||
authentication (cert. is set using tls_client directive). 'plain' performs a
|
||||
simple bind using provided credentials.
|
||||
|
||||
*Syntax:* dn_template _template_
|
||||
|
||||
DN template to use for binding. '{username}' is replaced with the
|
||||
username specified by the user.
|
||||
|
||||
*Syntax:* base_dn _dn_
|
||||
|
||||
Base DN to use for lookup.
|
||||
|
||||
*Syntax:* filter _str_
|
||||
|
||||
DN lookup filter. '{username}' is replaced with the username specified
|
||||
by the user.
|
||||
|
||||
Example:
|
||||
```
|
||||
(&(objectClass=posixAccount)(uid={username}))
|
||||
```
|
||||
|
||||
Example (using ActiveDirectory):
|
||||
```
|
||||
(&(objectCategory=Person)(memberOf=CN=user-group,OU=example,DC=example,DC=org)(sAMAccountName={username})(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
(&(objectClass=Person)(mail={username}))
|
||||
```
|
||||
|
||||
*Syntax:* starttls _bool_ ++
|
||||
*Default:* off
|
||||
|
||||
Whether to upgrade connection to TLS using STARTTLS.
|
||||
|
||||
*Syntax:* tls_client { ... }
|
||||
|
||||
Advanced TLS client configuration. See *maddy-tls*(5) for details.
|
||||
|
||||
*Syntax:* connect_timeout _duration_ ++
|
||||
*Default:* 1m
|
||||
|
||||
Timeout for initial connection to the directory server.
|
||||
|
||||
*Syntax:* request_timeout _duration_ ++
|
||||
*Default:* 1m
|
||||
|
||||
Timeout for each request (binding, lookup).
|
113
docs/man/maddy-blob.5.scd
Normal file
113
docs/man/maddy-blob.5.scd
Normal file
|
@ -0,0 +1,113 @@
|
|||
maddy-blob(5) "maddy mail server" "maddy reference documentation"
|
||||
|
||||
; TITLE Message blob storage
|
||||
|
||||
Some IMAP storage backends support pluggable message storage that allows
|
||||
message contents to be stored separately from IMAP index.
|
||||
|
||||
Modules described in this page are what can be used with such storage backends.
|
||||
In most cases they have to be specified using the 'msg_store' directive, like
|
||||
this:
|
||||
```
|
||||
storage.imapsql local_mailboxes {
|
||||
msg_store fs /var/lib/email
|
||||
}
|
||||
```
|
||||
|
||||
Unless explicitly configured, storage backends with pluggable storage will
|
||||
store messages in state_dir/messages (e.g. /var/lib/maddy/messages) FS
|
||||
directory.
|
||||
|
||||
# FS directory storage (storage.blob.fs)
|
||||
|
||||
This module stores message bodies in a file system directory.
|
||||
|
||||
```
|
||||
storage.blob.fs {
|
||||
root <directory>
|
||||
}
|
||||
```
|
||||
```
|
||||
storage.blob.fs <directory>
|
||||
```
|
||||
|
||||
## Configuration directives
|
||||
|
||||
*Syntax:* root _path_ ++
|
||||
*Default:* not set
|
||||
|
||||
Path to the FS directory. Must be readable and writable by the server process.
|
||||
If it does not exist - it will be created (parent directory should be writable
|
||||
for this). Relative paths are interpreted relatively to server state directory.
|
||||
|
||||
# Amazon S3 storage (storage.blob.s3)
|
||||
|
||||
This modules stores messages bodies in a bucket on S3-compatible storage.
|
||||
|
||||
```
|
||||
storage.blob.s3 {
|
||||
endpoint play.min.io
|
||||
secure yes
|
||||
access_key "Q3AM3UQ867SPQQA43P2F"
|
||||
secret_key "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
|
||||
bucket maddy-test
|
||||
|
||||
# optional
|
||||
region eu-central-1
|
||||
object_prefix maddy/
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
storage.imapsql local_mailboxes {
|
||||
...
|
||||
msg_store s3 {
|
||||
endpoint s3.amazonaws.com
|
||||
access_key "..."
|
||||
secret_key "..."
|
||||
bucket maddy-messages
|
||||
region us-west-2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration directives
|
||||
|
||||
*Syntax:* endpoint _address:port_
|
||||
|
||||
REQUIRED.
|
||||
|
||||
Root S3 endpoint. e.g. s3.amazonaws.com
|
||||
|
||||
*Syntax:* secure _boolean_ ++
|
||||
*Default:* yes
|
||||
|
||||
Whether TLS should be used.
|
||||
|
||||
*Syntax:* access_key _string_ ++
|
||||
*Syntax:* secret_key _string_
|
||||
|
||||
REQUIRED.
|
||||
|
||||
Static S3 credentials.
|
||||
|
||||
*Syntax:* bucket _name_
|
||||
|
||||
REQUIRED.
|
||||
|
||||
S3 bucket name. The bucket must exist and
|
||||
be read-writable.
|
||||
|
||||
*Syntax:* region _string_ ++
|
||||
*Default:* not set
|
||||
|
||||
S3 bucket location. May be called "endpoint"
|
||||
in some manuals.
|
||||
|
||||
*Syntax:* object_prefix _string_ ++
|
||||
*Default:* empty string
|
||||
|
||||
String to add to all keys stored by maddy.
|
||||
|
||||
Can be useful when S3 is used as a file system.
|
|
@ -55,14 +55,6 @@ Action to take when check fails. See Check actions for details.
|
|||
Log both sucessfull and unsucessfull check executions instead of just
|
||||
unsucessfull.
|
||||
|
||||
## require_matching_ehlo
|
||||
|
||||
Check that source server hostname (from EHLO/HELO command) resolves to source
|
||||
server IP.
|
||||
|
||||
By default, quarantines messages coming from servers with mismatched
|
||||
EHLO hostname, use 'fail_action' directive to change that.
|
||||
|
||||
## require_mx_record
|
||||
|
||||
Check that domain in MAIL FROM command does have a MX record and none of them
|
||||
|
@ -871,3 +863,91 @@ X-Spam-Flag and X-Spam-Score are added to the header irregardless of value.
|
|||
|
||||
Flags to pass to the rspamd server.
|
||||
See https://rspamd.com/doc/architecture/protocol.html for details.
|
||||
|
||||
## MAIL FROM and From authorization (check.authorize_sender)
|
||||
|
||||
This check verifies that envelope and header sender addresses belong
|
||||
to the authenticated user. Address ownership is established via table
|
||||
that maps each user account to a email address it is allowed to use.
|
||||
There are some special cases, see user_to_email description below.
|
||||
|
||||
```
|
||||
check.authorize_sender {
|
||||
prepare_email identity
|
||||
user_to_email identity
|
||||
check_header yes
|
||||
|
||||
unauth_action reject
|
||||
no_match_action reject
|
||||
malformed_action reject
|
||||
err_action reject
|
||||
|
||||
auth_normalize precis_casefold_email
|
||||
from_normalize precis_casefold_email
|
||||
}
|
||||
```
|
||||
```
|
||||
check {
|
||||
authorize_sender { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration directives
|
||||
|
||||
*Syntax:* user_to_email _table_ ++
|
||||
*Default:* identity
|
||||
|
||||
Table to use for lookups. Result of the lookup should contain either the
|
||||
domain name, the full email address or "*" string. If it is just domain - user
|
||||
will be allowed to use any mailbox within a domain as a sender address.
|
||||
If result contains "*" - user will be allowed to use any address.
|
||||
|
||||
*Syntax:* check_header _boolean_ ++
|
||||
*Default:* yes
|
||||
|
||||
Whether to verify header sender in addition to envelope.
|
||||
|
||||
Either Sender or From field value should match the
|
||||
authorization identity.
|
||||
|
||||
*Syntax:* unauth_action _action_ ++
|
||||
*Default:* reject
|
||||
|
||||
What to do if the user is not authenticated at all.
|
||||
|
||||
*Syntax:* no_match_action _action_ ++
|
||||
*Default:* reject
|
||||
|
||||
What to do if user is not allowed to use the sender address specified.
|
||||
|
||||
*Syntax:* malformed_action _action_ ++
|
||||
*Default:* reject
|
||||
|
||||
What to do if From or Sender header fields contain malformed values.
|
||||
|
||||
*Syntax:* err_action _action_ ++
|
||||
*Default:* reject
|
||||
|
||||
What to do if error happens during prepare_email or user_to_email lookup.
|
||||
|
||||
*Syntax:* auth_normalize _action_ ++
|
||||
*Default:* precis_casefold_email
|
||||
|
||||
Normalization function to apply to authorization username before
|
||||
further processing.
|
||||
|
||||
Available options:
|
||||
- precis_casefold_email PRECIS UsernameCaseMapped profile + U-labels form for domain
|
||||
- precis_casefold PRECIS UsernameCaseMapped profile for the entire string
|
||||
- precis_email PRECIS UsernameCasePreserved profile + U-labels form for domain
|
||||
- precis PRECIS UsernameCasePreserved profile for the entire string
|
||||
- casefold Convert to lower case
|
||||
- noop Nothing
|
||||
|
||||
*Syntax:* from_normalize _action_ ++
|
||||
*Default:* precis_casefold_email
|
||||
|
||||
Normalization function to apply to email addresses before
|
||||
further processing.
|
||||
|
||||
Available options are same as for auth_normalize.
|
|
@ -21,12 +21,11 @@ created.
|
|||
|
||||
# SQL-based database module (storage.imapsql)
|
||||
|
||||
The imapsql module implements unified database for IMAP index and message
|
||||
The imapsql module implements database for IMAP index and message
|
||||
metadata using SQL-based relational database.
|
||||
|
||||
Message contents are stored in an "external store", currently the only
|
||||
supported "external store" is a filesystem directory, used by default.
|
||||
By default, all messages are stored in StateDirectory/messages under random IDs.
|
||||
Message contents are stored in an "external store" defined by msg_store
|
||||
directive. By default this is a file system directory under /var/lib/maddy.
|
||||
|
||||
Supported RDBMS:
|
||||
- SQLite 3.25.0
|
||||
|
@ -40,6 +39,7 @@ PRECIS UsernameCaseMapped profile.
|
|||
storage.imapsql {
|
||||
driver sqlite3
|
||||
dsn imapsql.db
|
||||
msg_store fs messages/
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -88,10 +88,12 @@ For PostgreSQL: https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parame
|
|||
|
||||
Should be specified either via an argument or via this directive.
|
||||
|
||||
*Syntax*: fsstore _directory_ ++
|
||||
*Default*: messages/
|
||||
*Syntax*: msg_store _store_ ++
|
||||
*Default*: fs messages/
|
||||
|
||||
Directory to store message contents in.
|
||||
Module to use for message bodies storage.
|
||||
|
||||
See *maddy-blob*(5) for details.
|
||||
|
||||
*Syntax*: ++
|
||||
compression off ++
|
||||
|
@ -154,3 +156,46 @@ imap_filter {
|
|||
command /etc/maddy/sieve.sh {account_name}
|
||||
}
|
||||
```
|
||||
|
||||
*Syntax:* delivery_map *table* ++
|
||||
*Default:* identity
|
||||
|
||||
Use specified table module (*maddy-tables*(5)) to map recipient
|
||||
addresses from incoming messages to mailbox names.
|
||||
|
||||
Normalization algorithm specified in delivery_normalize is appied before
|
||||
delivery_map.
|
||||
|
||||
*Syntax:* delivery_normalize _name_ ++
|
||||
*Default:* precis_casefold_email
|
||||
|
||||
Normalization function to apply to email addresses before mapping them
|
||||
to mailboxes.
|
||||
|
||||
See auth_normalize.
|
||||
|
||||
*Syntax*: auth_map *table* ++
|
||||
*Default*: identity
|
||||
|
||||
Use specified table module (*maddy-tables*(5)) to map authentication
|
||||
usernames to mailbox names.
|
||||
|
||||
Normalization algorithm specified in auth_normalize is applied before
|
||||
auth_map.
|
||||
|
||||
*Syntax*: auth_normalize _name_ ++
|
||||
*Default*: precis_casefold_email
|
||||
|
||||
Normalization function to apply to authentication usernames before mapping
|
||||
them to mailboxes.
|
||||
|
||||
Available options:
|
||||
- precis_casefold_email PRECIS UsernameCaseMapped profile + U-labels form for domain
|
||||
- precis_casefold PRECIS UsernameCaseMapped profile for the entire string
|
||||
- precis_email PRECIS UsernameCasePreserved profile + U-labels form for domain
|
||||
- precis PRECIS UsernameCasePreserved profile for the entire string
|
||||
- casefold Convert to lower case
|
||||
- noop Nothing
|
||||
|
||||
Note: On message delivery, recipient address is unconditionally normalized
|
||||
using precis_casefold_email function.
|
||||
|
|
|
@ -56,6 +56,13 @@ aaa: bbb
|
|||
# That is, the following line is equivalent to
|
||||
# aaa:
|
||||
aaa
|
||||
|
||||
# If the same key is used multiple times - table.file will return
|
||||
# multiple values when queries. Note that this is not used by
|
||||
# most modules. E.g. replace_rcpt does not (intentionally) support
|
||||
# 1-to-N alias expansion.
|
||||
ddd: firstvalue
|
||||
ddd: secondvalue
|
||||
```
|
||||
|
||||
# SQL query mapping (table.sql_query)
|
||||
|
@ -254,3 +261,54 @@ The module 'dummy' represents an empty table.
|
|||
```
|
||||
dummy { }
|
||||
```
|
||||
|
||||
# Email local part (table.email_localpart)
|
||||
|
||||
The module 'email_localpart' extracts and unescaped local ("username") part
|
||||
of the email address.
|
||||
|
||||
E.g.
|
||||
test@example.org => test
|
||||
"test @ a"@example.org => test @ a
|
||||
|
||||
```
|
||||
table.email_localpart { }
|
||||
```
|
||||
|
||||
# Table chaining module (table.chain)
|
||||
|
||||
The table.chain module allows chaining together multiple table modules
|
||||
by using value returned by a previous table as an input for the second
|
||||
table.
|
||||
|
||||
Example:
|
||||
```
|
||||
table.chain {
|
||||
step regexp "(.+)(\\+[^+"@]+)?@example.org" "$1@example.org"
|
||||
step file /etc/maddy/emails
|
||||
}
|
||||
```
|
||||
This will strip +prefix from mailbox before looking it up
|
||||
in /etc/maddy/emails list.
|
||||
|
||||
## Configuration directives
|
||||
|
||||
*Syntax*: step _table_
|
||||
|
||||
Adds a table module to the chain. If input value is not in the table
|
||||
(e.g. file) - return "not exists" error.
|
||||
|
||||
*Syntax*: optional_step _table_
|
||||
|
||||
Same as step but if input value is not in the table - it is passed to the
|
||||
next step without changes.
|
||||
|
||||
Example:
|
||||
Something like this can be used to map emails to usernames
|
||||
after translating them via aliases map:
|
||||
```
|
||||
table.chain {
|
||||
optional_step file /etc/maddy/aliases
|
||||
step regexp "(.+)@(.+)" "$1"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -128,6 +128,34 @@ per-source/per-destination are as observed when message exits the server.
|
|||
|
||||
Choose the local IP to bind for outbound SMTP connections.
|
||||
|
||||
*Syntax*: connect_timeout _duration_ ++
|
||||
*Default*: 5m
|
||||
|
||||
Timeout for TCP connection establishment.
|
||||
|
||||
RFC 5321 recommends 5 minutes for "initial greeting" that includes TCP
|
||||
handshake. maddy uses two separate timers - one for "dialing" (DNS A/AAAA
|
||||
lookup + TCP handshake) and another for "initial greeting". This directive
|
||||
configures the former. The latter is not configurable and is hardcoded to be
|
||||
5 minutes.
|
||||
|
||||
*Syntax*: command_timeout _duration_ ++
|
||||
*Default*: 5m
|
||||
|
||||
Timeout for any SMTP command (EHLO, MAIL, RCPT, DATA, etc).
|
||||
|
||||
If STARTTLS is used this timeout also applies to TLS handshake.
|
||||
|
||||
RFC 5321 recommends 5 minutes for MAIL/RCPT and 3 minutes for
|
||||
DATA.
|
||||
|
||||
*Syntax*: submission_timeout _duration_ ++
|
||||
*Default*: 12m
|
||||
|
||||
Time to wait after the entire message is sent (after "final dot").
|
||||
|
||||
RFC 5321 recommends 10 minutes.
|
||||
|
||||
*Syntax*: debug _boolean_ ++
|
||||
*Default*: global directive value
|
||||
|
||||
|
@ -333,6 +361,9 @@ target.smtp {
|
|||
require_yes no
|
||||
auth off
|
||||
targets tcp://127.0.0.1:2525
|
||||
connect_timeout 5m
|
||||
command_timeout 5m
|
||||
submission_timeout 12m
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -405,6 +436,21 @@ TLS).
|
|||
Multiple addresses can be specified, they will be tried in order until connection to
|
||||
one succeeds (including TLS handshake if TLS is required).
|
||||
|
||||
*Syntax*: connect_timeout _duration_ ++
|
||||
*Default*: 5m
|
||||
|
||||
Same as for target.remote.
|
||||
|
||||
*Syntax*: command_timeout _duration_ ++
|
||||
*Default*: 5m
|
||||
|
||||
Same as for target.remote.
|
||||
|
||||
*Syntax*: submission_timeout _duration_ ++
|
||||
*Default*: 12m
|
||||
|
||||
Same as for target.remote.
|
||||
|
||||
# LMTP transparent forwarding module (target.lmtp)
|
||||
|
||||
The 'target.lmtp' module is similar to 'target.smtp' and supports all
|
||||
|
|
|
@ -35,6 +35,12 @@ tls {
|
|||
|
||||
If multiple certificates are listed, SNI will be used.
|
||||
|
||||
- acme
|
||||
|
||||
Automatically obtains a certificate using ACME protocol (Let's Encrypt)
|
||||
|
||||
See below for details.
|
||||
|
||||
- off
|
||||
|
||||
Not really a loader but a special value for tls directive, explicitly disables TLS for
|
||||
|
@ -152,3 +158,207 @@ server certificates.
|
|||
Present the specified certificate when server requests a client certificate.
|
||||
Files should use PEM format. Both directives should be specified.
|
||||
|
||||
# Automatic certificate management via ACME
|
||||
|
||||
```
|
||||
tls.loader.acme {
|
||||
debug off
|
||||
hostname example.maddy.invalid
|
||||
store_path /var/lib/maddy/acme
|
||||
ca https://acme-v02.api.letsencrypt.org/directory
|
||||
test_ca https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
email test@maddy.invalid
|
||||
agreed off
|
||||
challenge dns-01
|
||||
dns ...
|
||||
}
|
||||
```
|
||||
|
||||
Maddy supports obtaining certificates using ACME protocol.
|
||||
|
||||
To use it, create a configuration name for tls.loader.acme
|
||||
and reference it from endpoints that should use automatically
|
||||
configured certificates:
|
||||
```
|
||||
tls.loader.acme local_tls {
|
||||
email put-your-email-here@example.org
|
||||
agreed # indicate your agreement with Let's Encrypt ToS
|
||||
challenge dns-01
|
||||
}
|
||||
|
||||
smtp tcp://127.0.0.1:25 {
|
||||
tls &local_tls
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Currently the only supported challenge is dns-01 one therefore
|
||||
you also need to configure the DNS provider:
|
||||
```
|
||||
tls.loader.acme local_tls {
|
||||
email maddy-acme@example.org
|
||||
agreed
|
||||
challenge dns-01
|
||||
dns PROVIDER_NAME {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
See below for supported providers and necessary configuration
|
||||
for each.
|
||||
|
||||
## Configuration directives
|
||||
|
||||
*Syntax:* debug _boolean_ ++
|
||||
*Default:* global directive value
|
||||
|
||||
Enable debug logging.
|
||||
|
||||
*Syntax:* hostname _str_ ++
|
||||
*Default:* global directive value
|
||||
|
||||
Domain name to issue certificate for. Required.
|
||||
|
||||
*Syntax:* store_path _path_ ++
|
||||
*Default:* state_dir/acme
|
||||
|
||||
Where to store issued certificates and associated metadata.
|
||||
Currently only filesystem-based store is supported.
|
||||
|
||||
*Syntax:* ca _url_ ++
|
||||
*Default:* Let's Encrypt production CA
|
||||
|
||||
URL of ACME directory to use.
|
||||
|
||||
*Syntax:* test_ca _url_ ++
|
||||
*Default:* Let's Encrypt staging CA
|
||||
|
||||
URL of ACME directory to use for retries should
|
||||
primary CA fail.
|
||||
|
||||
maddy will keep attempting to issues certificates
|
||||
using test_ca until it succeeds then it will switch
|
||||
back to the one configured via 'ca' option.
|
||||
|
||||
This avoids rate limit issues with production CA.
|
||||
|
||||
*Syntax:* email _str_ ++
|
||||
*Default:* not set
|
||||
|
||||
Email to pass while registering an ACME account.
|
||||
|
||||
*Syntax:* agreed _boolean_ ++
|
||||
*Default:* false
|
||||
|
||||
Whether you agreed to ToS of the CA service you are using.
|
||||
|
||||
*Syntax:* challenge dns-01 ++
|
||||
*Default:* not set
|
||||
|
||||
Challenge(s) to use while performing domain verification.
|
||||
|
||||
## DNS providers
|
||||
|
||||
Support for some providers is not provided by standard builds.
|
||||
To be able to use these, you need to compile maddy
|
||||
with "libdns_PROVIDER" build tag.
|
||||
E.g.
|
||||
```
|
||||
./build.sh -tags 'libdns_googleclouddns'
|
||||
```
|
||||
|
||||
- gandi
|
||||
|
||||
```
|
||||
dns gandi {
|
||||
api_token "token"
|
||||
}
|
||||
```
|
||||
|
||||
- digitalocean
|
||||
|
||||
```
|
||||
dns digitalocean {
|
||||
api_token "..."
|
||||
}
|
||||
```
|
||||
|
||||
- cloudflare
|
||||
|
||||
See https://github.com/libdns/cloudflare#authenticating
|
||||
|
||||
```
|
||||
dns cloudflare {
|
||||
api_token "..."
|
||||
}
|
||||
```
|
||||
|
||||
- vultr
|
||||
|
||||
```
|
||||
dns vultr {
|
||||
api_token "..."
|
||||
}
|
||||
```
|
||||
|
||||
- hetzner
|
||||
|
||||
```
|
||||
dns hetzner {
|
||||
api_token "..."
|
||||
}
|
||||
```
|
||||
|
||||
- googleclouddns (non-default)
|
||||
|
||||
```
|
||||
dns googleclouddns {
|
||||
project "project_id"
|
||||
service_account_json "path"
|
||||
}
|
||||
```
|
||||
|
||||
- route53 (non-default)
|
||||
|
||||
```
|
||||
dns route53 {
|
||||
secret_access_key "..."
|
||||
access_key_id "..."
|
||||
# or use environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
|
||||
}
|
||||
```
|
||||
|
||||
- leaseweb (non-default)
|
||||
|
||||
```
|
||||
dns leaseweb {
|
||||
api_key "key"
|
||||
}
|
||||
```
|
||||
|
||||
- metaname (non-default)
|
||||
|
||||
```
|
||||
dns metaname {
|
||||
api_key "key"
|
||||
account_ref "reference"
|
||||
}
|
||||
```
|
||||
|
||||
- alidns (non-default)
|
||||
|
||||
```
|
||||
dns alidns {
|
||||
key_id "..."
|
||||
key_secret "..."
|
||||
}
|
||||
```
|
||||
|
||||
- namedotcom (non-default)
|
||||
|
||||
```
|
||||
dns namedotcom {
|
||||
user "..."
|
||||
token "..."
|
||||
}
|
||||
```
|
||||
|
|
43
docs/multiple-domains.md
Normal file
43
docs/multiple-domains.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Multiple domains configuration
|
||||
|
||||
## Separate account namespaces
|
||||
|
||||
Given two domains, example.org and example.com. foo@example.org and
|
||||
foo@example.com are different and completely independent accounts.
|
||||
|
||||
All changes needed to make it work is to make sure all domains are specified in
|
||||
the `$(local_domains)` macro in the main configuration file. Note that you need
|
||||
to pick one domain as a "primary" for use in auto-generated messages.
|
||||
```
|
||||
$(primary_domain) = example.org
|
||||
$(local_domains) = $(primary_domain) example.com
|
||||
```
|
||||
|
||||
The base configuration is done. You can create accounts using maddyctl using
|
||||
both domains in the name, send and receive messages and so on. Do not forget
|
||||
to configure corresponding SPF, DMARC and MTA-STS records as was
|
||||
recommended in the [introduction tutorial](tutorials/setting-up.md).
|
||||
|
||||
## Single account namespace
|
||||
|
||||
You can configure maddy to only use local part of the email
|
||||
as an account identifier instead of the complete email.
|
||||
|
||||
This needs two changes to default configuration:
|
||||
```
|
||||
storage.imapsql local_mailboxes {
|
||||
...
|
||||
delivery_map email_localpart
|
||||
auth_normalize precis_casefold
|
||||
}
|
||||
```
|
||||
|
||||
After that you can create accounts without specifying the domain part:
|
||||
```
|
||||
maddyctl imap-acct create foxcpp
|
||||
maddyctl creds create foxcpp
|
||||
```
|
||||
And authenticate using "foxcpp" in email clients.
|
||||
|
||||
Messages for any foxcpp@* address with a domain in `$(local_domains)`
|
||||
will be delivered to that mailbox.
|
|
@ -1,56 +0,0 @@
|
|||
# Multiple domains configuration
|
||||
|
||||
## Separate account namespaces
|
||||
|
||||
Given two domains, example.org and example.com. foo@example.org and
|
||||
foo@example.com are different and completely independent accounts.
|
||||
|
||||
All changes needed to make it work is to make sure all domains are specified in
|
||||
the `$(local_domains)` macro in the main configuration file. Note that you need
|
||||
to pick one domain as a "primary" for use in auto-generated messages.
|
||||
```
|
||||
$(primary_domain) = example.org
|
||||
$(local_domains) = $(primary_domain) example.com
|
||||
```
|
||||
|
||||
The base configuration is done. You can create accounts using maddyctl using
|
||||
both domains in the name, send and receive messages and so on. Do not forget
|
||||
to configure corresponding SPF, DMARC and MTA-STS records as was
|
||||
recommended in the [introduction tutorial](setting-up.md).
|
||||
|
||||
## Single account namespace
|
||||
|
||||
Lets say you want to handle messages for domains example.org and example.com
|
||||
and make that foo@example.org and foo@example.com are the same accounts.
|
||||
Sadly, this case is not very well-supported by maddy, but it still can be
|
||||
implemented.
|
||||
|
||||
You already should have the primary domain set for autogenerated messages and
|
||||
so on. The idea is to redirect all messages from non-primary domains to the
|
||||
primary one.
|
||||
|
||||
For each handled domain, the following line should be added to the
|
||||
`modify` block that gets applied for local recipients:
|
||||
```
|
||||
replace_rcpt regexp /(.+)@example.com/ $1@$(primary_domain)
|
||||
```
|
||||
It does regexp replacement, turning anything@example.com into
|
||||
anything@$(primary_domain) where $(primary_domain) in our case is example.org.
|
||||
|
||||
E.g.
|
||||
```
|
||||
$(primary_domain) = example.org
|
||||
|
||||
# Probably somewhere in local_routing
|
||||
modify {
|
||||
replace_rcpt regexp /(.+)@example.net/ $1@$(primary_domain)
|
||||
replace_rcpt regexp /(.+)@example.com/ $1@$(primary_domain)
|
||||
}
|
||||
```
|
||||
With that configuration, all messages for foo@example.net and foo@example.com
|
||||
will end up in the foo@example.org mailbox.
|
||||
|
||||
Note, however, no account credentials aliasing is done. Users should always use
|
||||
the account name with the primary domain to access IMAP mailboxes.
|
||||
|
||||
**Note 1**: All domains should still be listed in the `$(local_domains)` macro.
|
98
docs/tutorials/pam.md
Normal file
98
docs/tutorials/pam.md
Normal file
|
@ -0,0 +1,98 @@
|
|||
# Using PAM authentication
|
||||
|
||||
maddy supports user authentication using PAM infrastructure via `auth.pam`
|
||||
module.
|
||||
|
||||
In order to use it, however, either maddy itself should be compiled
|
||||
with libpam support or a helper executable should be built and
|
||||
installed into an appropriate directory.
|
||||
|
||||
It is recommended to use builtin libpam support if you are using
|
||||
PAM as an intermediate for authentication provider not directly
|
||||
supported by maddy.
|
||||
|
||||
If PAM authentication requires privileged access on the host system
|
||||
(e.g. pam_unix.so aka /etc/shadow) then it is recommended to use
|
||||
a privileged helper executable since maddy process itself won't
|
||||
have access to it.
|
||||
|
||||
## Built-in PAM support
|
||||
|
||||
Binary artifacts provided for releases do not come with
|
||||
libpam support. You should build maddy from source.
|
||||
|
||||
See [here](../building-from-source) for detailed instructions.
|
||||
|
||||
You should have libpam development files installed (`libpam-dev`
|
||||
package on Ubuntu/Debian).
|
||||
|
||||
Then add `--tags 'libpam'` to the build command:
|
||||
```
|
||||
./build.sh --tags 'libpam'
|
||||
```
|
||||
|
||||
Then you should be able to replace `local_authdb` implementation
|
||||
in default configuration with `auth.pam`:
|
||||
```
|
||||
auth.pam local_authdb {
|
||||
use_helper no
|
||||
}
|
||||
```
|
||||
|
||||
## Helper executable
|
||||
|
||||
TL;DR
|
||||
```
|
||||
git clone https://github.com/foxcpp/maddy
|
||||
cd maddy/cmd/maddy-pam-helper
|
||||
gcc pam.c main.c -lpam -o maddy-pam-helper
|
||||
```
|
||||
|
||||
Copy the resulting executable into /usr/lib/maddy/ and make
|
||||
it setuid-root so it can read /etc/shadow (if that's necessary):
|
||||
```
|
||||
chown root:maddy /usr/lib/maddy/maddy-pam-helper
|
||||
chmod u+xs,g+x,o-x /usr/lib/maddy/maddy-pam-helper
|
||||
```
|
||||
|
||||
Then you should be able to replace `local_authdb` implementation
|
||||
in default configuration with `auth.pam`:
|
||||
```
|
||||
auth.pam local_authdb {
|
||||
use_helper yes
|
||||
}
|
||||
```
|
||||
|
||||
## Account names
|
||||
|
||||
Since PAM does not use emails for authentication you should also
|
||||
switch storage backend to using usernames for authentication:
|
||||
```
|
||||
storage.imapsql local_mailboxes {
|
||||
...
|
||||
delivery_map email_localpart
|
||||
auth_normalize precis_casefold
|
||||
}
|
||||
```
|
||||
(See [Multiple domains](../../multiple-domains) for details)
|
||||
|
||||
## PAM service
|
||||
|
||||
You should create a PAM configuration file for maddy to use.
|
||||
Place it into /etc/pam.d/maddy.
|
||||
Here is the minimal example using pam_unix (shadow database).
|
||||
```
|
||||
#%PAM-1.0
|
||||
auth required pam_unix.so
|
||||
account required pam_unix.so
|
||||
```
|
||||
|
||||
Here is the configuration example you could use on Ubuntu
|
||||
to use the authentication config system itself uses:
|
||||
```
|
||||
#%PAM-1.0
|
||||
|
||||
@include common-auth
|
||||
@include common-account
|
||||
@include common-session
|
||||
```
|
|
@ -19,11 +19,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package address
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/dns"
|
||||
"golang.org/x/net/idna"
|
||||
"golang.org/x/text/secure/precis"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
|
@ -119,3 +121,41 @@ func FQDNDomain(addr string) string {
|
|||
}
|
||||
return addr + "."
|
||||
}
|
||||
|
||||
// PRECISFold applies UsernameCaseMapped to the local part and dns.ForLookup
|
||||
// to domain part of the address.
|
||||
func PRECISFold(addr string) (string, error) {
|
||||
return precisEmail(addr, precis.UsernameCaseMapped)
|
||||
}
|
||||
|
||||
// PRECIS applies UsernameCasePreserved to the local part and dns.ForLookup
|
||||
// to domain part of the address.
|
||||
func PRECIS(addr string) (string, error) {
|
||||
return precisEmail(addr, precis.UsernameCasePreserved)
|
||||
}
|
||||
|
||||
func precisEmail(addr string, profile *precis.Profile) (string, error) {
|
||||
mbox, domain, err := Split(addr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("address: precis: %w", err)
|
||||
}
|
||||
|
||||
// PRECISFold is not included in the regular address.ForLookup since it reduces
|
||||
// the range of valid addresses to a subset of actually valid values.
|
||||
// PRECISFold is a matter of our own local policy, not a general rule for all
|
||||
// email addresses.
|
||||
|
||||
// Side note: For used profiles, there is no practical difference between
|
||||
// CompareKey and String.
|
||||
mbox, err = profile.CompareKey(mbox)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("address: precis: %w", err)
|
||||
}
|
||||
|
||||
domain, err = dns.ForLookup(domain)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("address: precis: %w", err)
|
||||
}
|
||||
|
||||
return mbox + "@" + domain, nil
|
||||
}
|
||||
|
|
|
@ -20,8 +20,6 @@ package tls
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
|
@ -40,11 +38,10 @@ func (cfg *TLSConfig) Get() (*tls.Config, error) {
|
|||
}
|
||||
tlsCfg := cfg.baseCfg.Clone()
|
||||
|
||||
certs, err := cfg.loader.LoadCerts()
|
||||
err := cfg.loader.ConfigureTLS(tlsCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsCfg.Certificates = certs
|
||||
|
||||
return tlsCfg, nil
|
||||
}
|
||||
|
@ -52,7 +49,7 @@ func (cfg *TLSConfig) Get() (*tls.Config, error) {
|
|||
// TLSDirective reads the TLS configuration and adds the reload handler to
|
||||
// reread certificates on SIGUSR2.
|
||||
//
|
||||
// The returned value is *tls.TLSConfig with GetConfigForClient set.
|
||||
// The returned value is *tls.Config with GetConfigForClient set.
|
||||
// If the 'tls off' is used, returned value is nil.
|
||||
func TLSDirective(m *config.Map, node config.Node) (interface{}, error) {
|
||||
cfg, err := readTLSBlock(m.Globals, node)
|
||||
|
@ -80,11 +77,6 @@ func readTLSBlock(globals map[string]interface{}, blockNode config.Node) (*TLSCo
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(blockNode.Args[0]); err == nil || strings.Contains(blockNode.Args[0], "/") {
|
||||
log.Println("'tls cert_path key_path' syntax is deprecated, use 'tls file cert_path key_path'")
|
||||
blockNode.Args = append([]string{"file"}, blockNode.Args...)
|
||||
}
|
||||
|
||||
err := modconfig.ModuleFromNode("tls.loader", blockNode.Args, config.Node{}, globals, &loader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -98,7 +90,7 @@ func readTLSBlock(globals map[string]interface{}, blockNode config.Node) (*TLSCo
|
|||
return loader, nil
|
||||
}, func(m *config.Map, node config.Node) (interface{}, error) {
|
||||
var l module.TLSLoader
|
||||
err := modconfig.ModuleFromNode("tls.loader", blockNode.Args, config.Node{}, globals, &l)
|
||||
err := modconfig.ModuleFromNode("tls.loader", node.Args, node, globals, &l)
|
||||
return l, err
|
||||
}, &loader)
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Logger is the structure that writes formatted output to the underlying
|
||||
|
@ -51,6 +52,11 @@ type Logger struct {
|
|||
Fields map[string]interface{}
|
||||
}
|
||||
|
||||
func (l Logger) Zap() *zap.Logger {
|
||||
// TODO: Migrate to using zap natively.
|
||||
return zap.New(zapLogger{L: l})
|
||||
}
|
||||
|
||||
func (l Logger) Debugf(format string, val ...interface{}) {
|
||||
if !l.Debug {
|
||||
return
|
||||
|
|
57
framework/log/zap.go
Normal file
57
framework/log/zap.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// TODO: Migrate to using actual zapcore to improve logging performance
|
||||
|
||||
type zapLogger struct {
|
||||
L Logger
|
||||
}
|
||||
|
||||
func (l zapLogger) Enabled(level zapcore.Level) bool {
|
||||
if l.L.Debug {
|
||||
return true
|
||||
}
|
||||
return level > zapcore.DebugLevel
|
||||
}
|
||||
|
||||
func (l zapLogger) With(fields []zapcore.Field) zapcore.Core {
|
||||
enc := zapcore.NewMapObjectEncoder()
|
||||
for _, f := range fields {
|
||||
f.AddTo(enc)
|
||||
}
|
||||
newF := make(map[string]interface{}, len(l.L.Fields)+len(enc.Fields))
|
||||
for k, v := range l.L.Fields {
|
||||
newF[k] = v
|
||||
}
|
||||
for k, v := range enc.Fields {
|
||||
newF[k] = v
|
||||
}
|
||||
l.L.Fields = newF
|
||||
return l
|
||||
}
|
||||
|
||||
func (l zapLogger) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if l.Enabled(entry.Level) {
|
||||
return ce.AddCore(entry, l)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
func (l zapLogger) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||
enc := zapcore.NewMapObjectEncoder()
|
||||
for _, f := range fields {
|
||||
f.AddTo(enc)
|
||||
}
|
||||
if entry.LoggerName != "" {
|
||||
l.L.Name += "/" + entry.LoggerName
|
||||
}
|
||||
l.L.log(entry.Level == zapcore.DebugLevel, l.L.formatMsg(entry.Message, enc.Fields))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zapLogger) Sync() error {
|
||||
return nil
|
||||
}
|
29
framework/module/blob_store.go
Normal file
29
framework/module/blob_store.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package module
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Blob interface {
|
||||
Sync() error
|
||||
io.Writer
|
||||
io.Closer
|
||||
}
|
||||
|
||||
var ErrNoSuchBlob = errors.New("blob_store: no such object")
|
||||
|
||||
// BlobStore is the interface used by modules providing large binary object
|
||||
// storage.
|
||||
type BlobStore interface {
|
||||
Create(key string) (Blob, error)
|
||||
|
||||
// Open returns the reader for the object specified by
|
||||
// passed key.
|
||||
//
|
||||
// If no such object exists - ErrNoSuchBlob is returned.
|
||||
Open(key string) (io.ReadCloser, error)
|
||||
|
||||
// Delete removes a set of keys from store. Non-existent keys are ignored.
|
||||
Delete(keys []string) error
|
||||
}
|
|
@ -37,7 +37,7 @@ func (d *Dummy) AuthPlain(username, _ string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Dummy) Lookup(_ string) (string, bool, error) {
|
||||
func (d *Dummy) Lookup(_ context.Context, _ string) (string, bool, error) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,13 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// NoRun makes sure modules do not start any bacground tests.
|
||||
//
|
||||
// If it set - modules should not perform any actual work and should stop
|
||||
// once the configuration is read and verified to be correct.
|
||||
// TODO: Replace it with separation of Init and Run at interface level.
|
||||
NoRun = false
|
||||
|
||||
modules = make(map[string]FuncNewModule)
|
||||
endpoints = make(map[string]FuncNewEndpoint)
|
||||
modulesLock sync.RWMutex
|
||||
|
|
|
@ -18,13 +18,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
package module
|
||||
|
||||
// Tabele is the interface implemented by module that implementation string-to-string
|
||||
import "context"
|
||||
|
||||
// Table is the interface implemented by module that implementation string-to-string
|
||||
// translation.
|
||||
//
|
||||
// Modules implementing this interface should be registered with prefix
|
||||
// "table." in name.
|
||||
type Table interface {
|
||||
Lookup(s string) (string, bool, error)
|
||||
Lookup(ctx context.Context, s string) (string, bool, error)
|
||||
}
|
||||
|
||||
// MultiTable is the interface that module can implement in addition to Table
|
||||
// if it can provide multiple values as a lookup result.
|
||||
type MultiTable interface {
|
||||
LookupMulti(ctx context.Context, s string) ([]string, error)
|
||||
}
|
||||
|
||||
type MutableTable interface {
|
||||
|
|
|
@ -18,7 +18,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
|
||||
package module
|
||||
|
||||
import "crypto/tls"
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
// TLSLoader interface is module interface that can be used to supply TLS
|
||||
// certificates to TLS-enabled endpoints.
|
||||
|
@ -35,5 +37,5 @@ import "crypto/tls"
|
|||
// Modules implementing this interface should be registered with prefix
|
||||
// "tls.loader." in name.
|
||||
type TLSLoader interface {
|
||||
LoadCerts() ([]tls.Certificate, error)
|
||||
ConfigureTLS(c *tls.Config) error
|
||||
}
|
||||
|
|
28
go.mod
28
go.mod
|
@ -5,6 +5,7 @@ go 1.14
|
|||
require (
|
||||
blitiri.com.ar/go/spf v1.2.0
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
|
||||
github.com/caddyserver/certmagic v0.14.1
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/emersion/go-imap v1.0.6
|
||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
|
||||
|
@ -18,29 +19,44 @@ require (
|
|||
github.com/emersion/go-milter v0.3.2
|
||||
github.com/emersion/go-msgauth v0.6.5
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||
github.com/emersion/go-smtp v0.15.0
|
||||
github.com/emersion/go-smtp v0.15.1-0.20210705155248-26eb4814e227
|
||||
github.com/foxcpp/go-dovecot-sasl v0.0.0-20200522223722-c4699d7a24bf
|
||||
github.com/foxcpp/go-imap-backend-tests v0.0.0-20200617132817-958ea5829771
|
||||
github.com/foxcpp/go-imap-i18nlevel v0.0.0-20200208001533-d6ec88553005
|
||||
github.com/foxcpp/go-imap-namespace v0.0.0-20200722130255-93092adf35f1
|
||||
github.com/foxcpp/go-imap-sql v0.4.1-0.20200823124337-2f57903a7ed0
|
||||
github.com/foxcpp/go-mockdns v0.0.0-20201212160233-ede2f9158d15
|
||||
github.com/foxcpp/go-mtasts v0.0.0-20191219193356-62bc3f1f74b8
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c
|
||||
github.com/klauspost/compress v1.11.13 // indirect
|
||||
github.com/lib/pq v1.10.0
|
||||
github.com/libdns/alidns v1.0.2
|
||||
github.com/libdns/cloudflare v0.1.0
|
||||
github.com/libdns/digitalocean v0.0.0-20210310230526-186c4ebd2215
|
||||
github.com/libdns/gandi v1.0.2
|
||||
github.com/libdns/googleclouddns v1.0.1
|
||||
github.com/libdns/hetzner v0.0.1
|
||||
github.com/libdns/leaseweb v0.2.1
|
||||
github.com/libdns/libdns v0.2.1
|
||||
github.com/libdns/metaname v0.3.0
|
||||
github.com/libdns/namedotcom v0.3.3
|
||||
github.com/libdns/route53 v1.1.1
|
||||
github.com/libdns/vultr v0.0.0-20201128180404-1d5ee21ea62f
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/miekg/dns v1.1.41
|
||||
github.com/miekg/dns v1.1.42
|
||||
github.com/minio/minio-go/v7 v7.0.12
|
||||
github.com/pierrec/lz4 v2.6.0+incompatible // indirect
|
||||
github.com/prometheus/client_golang v1.10.0
|
||||
github.com/prometheus/common v0.20.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/urfave/cli v1.22.5
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1
|
||||
go.uber.org/zap v1.17.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
|
||||
golang.org/x/text v0.3.6
|
||||
)
|
||||
|
|
495
go.sum
495
go.sum
|
@ -2,7 +2,49 @@ blitiri.com.ar/go/spf v1.2.0 h1:aPpeEVKz5Ue4xb4SEt4AzScCSyES7/pol6znzZGle3A=
|
|||
blitiri.com.ar/go/spf v1.2.0/go.mod h1:HLmgHxdrsqbBgi5omEopdAKm18PypvUKJGkF/j7BO0w=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.82.0 h1:FZ4B2YAzCzkwzGEOp1dqG8sAa3zNIvro1fHRTrB81RU=
|
||||
cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
|
@ -22,20 +64,31 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
|||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.30.27 h1:9gPjZWVDSoQrBO2AvqrWObS6KAZByfEJxQoCYo4ZfK0=
|
||||
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/caddyserver/certmagic v0.14.1 h1:8RIFS/LbGne/I7Op56Kkm2annnei7io9VW/IWDttE9U=
|
||||
github.com/caddyserver/certmagic v0.14.1/go.mod h1:oRQOZmUVKwlpgNidslysHt05osM9uMrJ4YMk+Ot4P4Q=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
|
@ -49,7 +102,11 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/digitalocean/godo v1.41.0 h1:WYy7MIVVhTMZUNB+UA3irl2V9FyDJeDttsifYyn7jYA=
|
||||
github.com/digitalocean/godo v1.41.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
|
@ -95,13 +152,18 @@ github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu
|
|||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
|
||||
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20210705155248-26eb4814e227 h1:LsNh1Xid8MkhJc0OiAbacbIUreFLdJHtERozP3/use0=
|
||||
github.com/emersion/go-smtp v0.15.1-0.20210705155248-26eb4814e227/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/foxcpp/go-dovecot-sasl v0.0.0-20200522223722-c4699d7a24bf h1:rmBPY5fryjp9zLQYsUmQqqgsYq7qeVfrjtr96Tf9vD8=
|
||||
|
@ -125,14 +187,22 @@ github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3g
|
|||
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
|
@ -143,35 +213,83 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
|||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
|
@ -183,10 +301,16 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
|||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
|
@ -202,9 +326,15 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
|||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c h1:lx/uPI+mUWlqEQ9e6CtNvaK/zD64s/mQ9+yMh16PgY0=
|
||||
github.com/johannesboyne/gofakes3 v0.0.0-20210704111953-6a9f95c2941c/go.mod h1:LIAXxPvcUXwOcTIj9LSNSUpE9/eMHalTWxsP/kmWxQI=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
|
@ -212,7 +342,11 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
|
|||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
|
@ -221,6 +355,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
|
||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
|
@ -232,6 +371,33 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||
github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/alidns v1.0.2 h1:WiT1cO2LWY95YNocTVBGipHjvRaFQOxMQ9X5bTiryRo=
|
||||
github.com/libdns/alidns v1.0.2/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/libdns/cloudflare v0.1.0 h1:93WkJaGaiXCe353LHEP36kAWCUw0YjFqwhkBkU2/iic=
|
||||
github.com/libdns/cloudflare v0.1.0/go.mod h1:a44IP6J1YH6nvcNl1PverfJviADgXUnsozR3a7vBKN8=
|
||||
github.com/libdns/digitalocean v0.0.0-20210310230526-186c4ebd2215 h1:JYi/h0UEECrxY2JCi5FIfZEDFuUJvwihUWdm3bnDu2A=
|
||||
github.com/libdns/digitalocean v0.0.0-20210310230526-186c4ebd2215/go.mod h1:GEZlJR69sPAUWjb77eLyeDczZNL+ezbo5UGIY2/xZXA=
|
||||
github.com/libdns/gandi v1.0.2 h1:1Ts8UpI1x5PVKpOjKC7Dn4+EObndz9gm6vdZnloHSKQ=
|
||||
github.com/libdns/gandi v1.0.2/go.mod h1:hxpbQKcQFgQrTS5lV4tAgn6QoL6HcCnoBJaW5nOW4Sk=
|
||||
github.com/libdns/googleclouddns v1.0.1 h1:g3BO+c4W4NYl8vkJk5sKLYwVTmOtGpnI2GryqSzJgkk=
|
||||
github.com/libdns/googleclouddns v1.0.1/go.mod h1:y6uAE0hE+uUwsP6BOm0Gym+I71gO65v9VZci25wRkkw=
|
||||
github.com/libdns/hetzner v0.0.1 h1:WsmcsOKnfpKmzwhfyqhGQEIlEeEaEUvb7ezoJgBKaqU=
|
||||
github.com/libdns/hetzner v0.0.1/go.mod h1:Jj12aJipO9Ir7OGaXueJ5J1RnerFMD0auGa6k9kujG4=
|
||||
github.com/libdns/leaseweb v0.2.1 h1:bQ759T44Tpmzd7mmMEgaLimSztPIRaMk1k6X4UXuJOA=
|
||||
github.com/libdns/leaseweb v0.2.1/go.mod h1:OeZtd+s2M1RfC3wIJF9SHZDFpD7H5RRiC6OPK3AWYjA=
|
||||
github.com/libdns/libdns v0.0.0-20200501023120-186724ffc821/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/metaname v0.3.0 h1:HJudLYthdv52TupOPczojip/nEQHW7xqk5+whGReva4=
|
||||
github.com/libdns/metaname v0.3.0/go.mod h1:a3hqEgj59tjWaWlF4WxQGhvMVtjz1E4Ngs1GfVS+VhQ=
|
||||
github.com/libdns/namedotcom v0.3.3 h1:R10C7+IqQGVeC4opHHMiFNBxdNBg1bi65ZwqLESl+jE=
|
||||
github.com/libdns/namedotcom v0.3.3/go.mod h1:GbYzsAF2yRUpI0WgIK5fs5UX+kDVUPaYCFLpTnKQm0s=
|
||||
github.com/libdns/route53 v1.1.1 h1:p9TC3KAewPraYB+AzbiS+9Ne1wwGa6JyQCBWPS2PiTo=
|
||||
github.com/libdns/route53 v1.1.1/go.mod h1:/uF0yuPxneTh3+WPLn8HoNElx3PJsBdxpuNhEFkM+4w=
|
||||
github.com/libdns/vultr v0.0.0-20201128180404-1d5ee21ea62f h1:i65uWz6ebW7T8bMqnJhzwj1B0HYB8mg/GXueGPxLrOs=
|
||||
github.com/libdns/vultr v0.0.0-20201128180404-1d5ee21ea62f/go.mod h1:T6u+iQbIf9wAQRE+MLDBHg0Xtjz2eWR1RTM1VbbDf1o=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
|
@ -250,20 +416,32 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
|
|||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/acmez v0.1.3 h1:J7MmNIk4Qf9b8mAGqAh4XkNeowv3f1zW816yf4zt7Qk=
|
||||
github.com/mholt/acmez v0.1.3/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
|
||||
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/minio-go/v7 v7.0.12 h1:/4pxUdwn9w0QEryNkrrWaodIESPRX+NxpO0Q6hVdaAA=
|
||||
github.com/minio/minio-go/v7 v7.0.12/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
|
@ -337,20 +515,29 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0=
|
||||
github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63/go.mod h1:n+VKSARF5y/tS9XFSP7vWDfS+GUC5vs/YT7M5XDTUEM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
|
@ -360,26 +547,53 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
|
||||
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/vultr/govultr/v2 v2.0.0 h1:+lAtqfWy3g9VwL7tT2Fpyad8Vv4MxOhT/NU8O5dk+EQ=
|
||||
github.com/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -389,18 +603,49 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -411,25 +656,66 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
|
|||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190310074541-c10a0554eabf/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 h1:4qWs8cYYH6PoEFy4dfhDFgoMGkwAcETd+MmPdCPMzUc=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -442,81 +728,252 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190308174544-00c44ba9c14f/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I=
|
||||
google.golang.org/api v0.47.0 h1:sQLWZQvP6jPGIP4JGPkJu4zHswrv81iobiyszr3b/0I=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210524171403-669157292da3 h1:xFyh6GBb+NO1L0xqb978I3sBPQpk6FrKO0jJGRvdj/0=
|
||||
google.golang.org/genproto v0.0.0-20210524171403-669157292da3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
|
@ -529,6 +986,9 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
|
|||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
|
@ -537,13 +997,24 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
|
|
1
internal/auth/external/externalauth.go
vendored
1
internal/auth/external/externalauth.go
vendored
|
@ -99,6 +99,5 @@ func (ea *ExternalAuth) AuthPlain(username, password string) error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("extauth", "auth.command", NewExternalAuth)
|
||||
module.Register("auth.external", NewExternalAuth)
|
||||
}
|
||||
|
|
280
internal/auth/ldap/ldap.go
Normal file
280
internal/auth/ldap/ldap.go
Normal file
|
@ -0,0 +1,280 @@
|
|||
package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
tls2 "github.com/foxcpp/maddy/framework/config/tls"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
const modName = "auth.ldap"
|
||||
|
||||
type Auth struct {
|
||||
instName string
|
||||
|
||||
urls []string
|
||||
readBind func(*ldap.Conn) error
|
||||
startls bool
|
||||
tlsCfg tls.Config
|
||||
dialer *net.Dialer
|
||||
requestTimeout time.Duration
|
||||
|
||||
dnTemplate string
|
||||
// or
|
||||
baseDN string
|
||||
filterTemplate string
|
||||
|
||||
conn *ldap.Conn
|
||||
connLock sync.Mutex
|
||||
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func New(modName, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
return &Auth{
|
||||
instName: instName,
|
||||
log: log.Logger{Name: modName},
|
||||
urls: inlineArgs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *Auth) Init(cfg *config.Map) error {
|
||||
a.dialer = &net.Dialer{}
|
||||
|
||||
cfg.Bool("debug", true, false, &a.log.Debug)
|
||||
cfg.Custom("tls_client", true, false, func() (interface{}, error) {
|
||||
return tls.Config{}, nil
|
||||
}, tls2.TLSClientBlock, &a.tlsCfg)
|
||||
cfg.Callback("urls", func(m *config.Map, node config.Node) error {
|
||||
a.urls = append(a.urls, node.Args...)
|
||||
return nil
|
||||
})
|
||||
cfg.Custom("bind", false, false, func() (interface{}, error) {
|
||||
return func(*ldap.Conn) error {
|
||||
return nil
|
||||
}, nil
|
||||
}, readBindDirective, &a.readBind)
|
||||
cfg.Bool("starttls", false, false, &a.startls)
|
||||
cfg.Duration("connect_timeout", false, false, time.Minute, &a.dialer.Timeout)
|
||||
cfg.Duration("request_timeout", false, false, time.Minute, &a.requestTimeout)
|
||||
cfg.String("dn_template", false, false, "", &a.dnTemplate)
|
||||
cfg.String("base_dn", false, false, "", &a.baseDN)
|
||||
cfg.String("filter", false, false, "", &a.filterTemplate)
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.dnTemplate == "" {
|
||||
if a.baseDN == "" {
|
||||
return fmt.Errorf("auth.ldap: base_dn not set")
|
||||
}
|
||||
if a.filterTemplate == "" {
|
||||
return fmt.Errorf("auth.ldap: filter not set")
|
||||
}
|
||||
} else {
|
||||
if a.baseDN != "" || a.filterTemplate != "" {
|
||||
return fmt.Errorf("auth.ldap: search directives set when dn_template is used")
|
||||
}
|
||||
}
|
||||
|
||||
if module.NoRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
a.conn, err = a.newConn()
|
||||
if err != nil {
|
||||
return fmt.Errorf("auth.ldap: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBindDirective(c *config.Map, n config.Node) (interface{}, error) {
|
||||
if len(n.Args) == 0 {
|
||||
return nil, fmt.Errorf("auth.ldap: auth expects at least one argument")
|
||||
}
|
||||
switch n.Args[0] {
|
||||
case "off":
|
||||
return func(*ldap.Conn) error { return nil }, nil
|
||||
case "unauth":
|
||||
return (*ldap.Conn).UnauthenticatedBind, nil
|
||||
case "plain":
|
||||
if len(n.Args) != 3 {
|
||||
return nil, fmt.Errorf("auth.ldap: username and password expected for plaintext bind")
|
||||
}
|
||||
return func(c *ldap.Conn) error {
|
||||
return c.Bind(n.Args[1], n.Args[2])
|
||||
}, nil
|
||||
case "external":
|
||||
return (*ldap.Conn).ExternalBind, nil
|
||||
}
|
||||
return nil, fmt.Errorf("auth.ldap: unknown bind authentication: %v", n.Args[0])
|
||||
}
|
||||
|
||||
func (a *Auth) Name() string {
|
||||
return modName
|
||||
}
|
||||
|
||||
func (a *Auth) InstanceName() string {
|
||||
return a.instName
|
||||
}
|
||||
|
||||
func (a *Auth) newConn() (*ldap.Conn, error) {
|
||||
var (
|
||||
conn *ldap.Conn
|
||||
tlsCfg *tls.Config
|
||||
)
|
||||
for _, u := range a.urls {
|
||||
parsedURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth.ldap: invalid server URL: %w", err)
|
||||
}
|
||||
hostname := parsedURL.Host
|
||||
tlsCfg = a.tlsCfg.Clone()
|
||||
a.tlsCfg.ServerName = hostname
|
||||
|
||||
conn, err = ldap.DialURL(u, ldap.DialWithDialer(a.dialer), ldap.DialWithTLSConfig(tlsCfg))
|
||||
if err != nil {
|
||||
a.log.Msg("cannot contact directory server", err, "url", u)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if conn == nil {
|
||||
return nil, fmt.Errorf("auth.ldap: all directory servers are unreachable")
|
||||
}
|
||||
|
||||
if a.requestTimeout != 0 {
|
||||
conn.SetTimeout(a.requestTimeout)
|
||||
}
|
||||
|
||||
if a.startls {
|
||||
if err := conn.StartTLS(tlsCfg); err != nil {
|
||||
return nil, fmt.Errorf("auth.ldap: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.readBind(conn); err != nil {
|
||||
return nil, fmt.Errorf("auth.ldap: %w", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (a *Auth) getConn() (*ldap.Conn, error) {
|
||||
a.connLock.Lock()
|
||||
if a.conn == nil {
|
||||
conn, err := a.newConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.conn = conn
|
||||
}
|
||||
if a.conn.IsClosing() {
|
||||
a.conn.Close()
|
||||
conn, err := a.newConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.conn = conn
|
||||
}
|
||||
return a.conn, nil
|
||||
}
|
||||
|
||||
func (a *Auth) returnConn(conn *ldap.Conn) {
|
||||
defer a.connLock.Unlock()
|
||||
if err := a.readBind(conn); err != nil {
|
||||
a.log.Error("failed to rebind for reading", err)
|
||||
conn.Close()
|
||||
a.conn = nil
|
||||
}
|
||||
if a.conn != conn {
|
||||
a.conn.Close()
|
||||
}
|
||||
a.conn = conn
|
||||
}
|
||||
|
||||
func (a *Auth) Lookup(_ context.Context, username string) (string, bool, error) {
|
||||
conn, err := a.getConn()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer a.returnConn(conn)
|
||||
|
||||
var userDN string
|
||||
if a.dnTemplate != "" {
|
||||
return "", false, fmt.Errorf("auth.ldap: lookups require search config but dn_template is used")
|
||||
} else {
|
||||
req := ldap.NewSearchRequest(
|
||||
a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
2, 0, false,
|
||||
strings.ReplaceAll(a.filterTemplate, "{username}", username),
|
||||
[]string{"dn"}, nil)
|
||||
res, err := conn.Search(req)
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("auth.ldap: search: %w", err)
|
||||
}
|
||||
if len(res.Entries) > 1 {
|
||||
return "", false, fmt.Errorf("auth.ldap: too manu entries returned (%d)", len(res.Entries))
|
||||
}
|
||||
if len(res.Entries) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
userDN = res.Entries[0].DN
|
||||
}
|
||||
|
||||
return userDN, true, nil
|
||||
}
|
||||
|
||||
func (a *Auth) AuthPlain(username, password string) error {
|
||||
conn, err := a.getConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.returnConn(conn)
|
||||
|
||||
var userDN string
|
||||
if a.dnTemplate != "" {
|
||||
userDN = strings.ReplaceAll(a.dnTemplate, "{username}", username)
|
||||
} else {
|
||||
req := ldap.NewSearchRequest(
|
||||
a.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
2, 0, false,
|
||||
strings.ReplaceAll(a.filterTemplate, "{username}", username),
|
||||
[]string{"dn"}, nil)
|
||||
res, err := conn.Search(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auth.ldap: search: %w", err)
|
||||
}
|
||||
if len(res.Entries) > 1 {
|
||||
return fmt.Errorf("auth.ldap: too manu entries returned (%d)", len(res.Entries))
|
||||
}
|
||||
if len(res.Entries) == 0 {
|
||||
return module.ErrUnknownCredentials
|
||||
}
|
||||
userDN = res.Entries[0].DN
|
||||
}
|
||||
|
||||
if err := conn.Bind(userDN, password); err != nil {
|
||||
return module.ErrUnknownCredentials
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
var _ module.PlainAuth = &Auth{}
|
||||
var _ module.Table = &Auth{}
|
||||
module.Register(modName, New)
|
||||
module.Register("table.ldap", New)
|
||||
}
|
|
@ -90,6 +90,5 @@ func (a *Auth) AuthPlain(username, password string) error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("pam", "auth.pam", New)
|
||||
module.Register("auth.pam", New)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package pass_table
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -63,13 +64,13 @@ func (a *Auth) InstanceName() string {
|
|||
return a.instName
|
||||
}
|
||||
|
||||
func (a *Auth) Lookup(username string) (string, bool, error) {
|
||||
func (a *Auth) Lookup(ctx context.Context, username string) (string, bool, error) {
|
||||
key, err := precis.UsernameCaseMapped.CompareKey(username)
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
return a.table.Lookup(key)
|
||||
return a.table.Lookup(ctx, key)
|
||||
}
|
||||
|
||||
func (a *Auth) AuthPlain(username, password string) error {
|
||||
|
@ -78,7 +79,7 @@ func (a *Auth) AuthPlain(username, password string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
hash, ok, err := a.table.Lookup(key)
|
||||
hash, ok, err := a.table.Lookup(context.TODO(), key)
|
||||
if !ok {
|
||||
return module.ErrUnknownCredentials
|
||||
}
|
||||
|
@ -121,7 +122,7 @@ func (a *Auth) CreateUser(username, password string) error {
|
|||
return fmt.Errorf("%s: create user %s (raw): %w", a.modName, username, err)
|
||||
}
|
||||
|
||||
_, ok, err = tbl.Lookup(key)
|
||||
_, ok, err = tbl.Lookup(context.TODO(), key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: create user %s: %w", a.modName, key, err)
|
||||
}
|
||||
|
@ -186,6 +187,5 @@ func (a *Auth) DeleteUser(username string) error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("pass_table", "auth.pass_table", New)
|
||||
module.Register("auth.pass_table", New)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package plain_separate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
|
@ -93,10 +94,10 @@ func (a *Auth) Init(cfg *config.Map) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *Auth) Lookup(username string) (string, bool, error) {
|
||||
func (a *Auth) Lookup(ctx context.Context, username string) (string, bool, error) {
|
||||
ok := len(a.userTbls) == 0
|
||||
for _, tbl := range a.userTbls {
|
||||
_, tblOk, err := tbl.Lookup(username)
|
||||
_, tblOk, err := tbl.Lookup(ctx, username)
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("plain_separate: underlying table error: %w", err)
|
||||
}
|
||||
|
@ -114,7 +115,7 @@ func (a *Auth) Lookup(username string) (string, bool, error) {
|
|||
func (a *Auth) AuthPlain(username, password string) error {
|
||||
ok := len(a.userTbls) == 0
|
||||
for _, tbl := range a.userTbls {
|
||||
_, tblOk, err := tbl.Lookup(username)
|
||||
_, tblOk, err := tbl.Lookup(context.TODO(), username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -140,6 +141,5 @@ func (a *Auth) AuthPlain(username, password string) error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("plain_separate", "auth.plain_separate", NewAuth)
|
||||
module.Register("auth.plain_separate", NewAuth)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package plain_separate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
|
@ -46,7 +47,7 @@ type mockTable struct {
|
|||
db map[string]string
|
||||
}
|
||||
|
||||
func (m mockTable) Lookup(a string) (string, bool, error) {
|
||||
func (m mockTable) Lookup(_ context.Context, a string) (string, bool, error) {
|
||||
b, ok := m.db[a]
|
||||
return b, ok, nil
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
|
||||
var (
|
||||
ErrUnsupportedMech = errors.New("Unsupported SASL mechanism")
|
||||
ErrInvalidAuthCred = errors.New("auth: invalid credentials")
|
||||
)
|
||||
|
||||
// SASLAuth is a wrapper that initializes sasl.Server using authenticators that
|
||||
|
@ -63,14 +64,10 @@ func (s *SASLAuth) AuthPlain(username, password string) error {
|
|||
|
||||
var lastErr error
|
||||
for _, p := range s.Plain {
|
||||
err := p.AuthPlain(username, password)
|
||||
if err == nil {
|
||||
lastErr = p.AuthPlain(username, password)
|
||||
if lastErr == nil {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("no auth. provider accepted creds, last err: %w", lastErr)
|
||||
|
@ -88,7 +85,7 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i
|
|||
err := s.AuthPlain(username, password)
|
||||
if err != nil {
|
||||
s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr)
|
||||
return errors.New("auth: invalid credentials")
|
||||
return ErrInvalidAuthCred
|
||||
}
|
||||
|
||||
return successCb(identity)
|
||||
|
@ -98,7 +95,7 @@ func (s *SASLAuth) CreateSASL(mech string, remoteAddr net.Addr, successCb func(i
|
|||
err := s.AuthPlain(username, password)
|
||||
if err != nil {
|
||||
s.Log.Error("authentication failed", err, "username", username, "src_ip", remoteAddr)
|
||||
return errors.New("auth: invalid credentials")
|
||||
return ErrInvalidAuthCred
|
||||
}
|
||||
|
||||
return successCb(username)
|
||||
|
|
|
@ -130,6 +130,5 @@ func (a *Auth) AuthPlain(username, password string) error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("shadow", "auth.shadow", New)
|
||||
module.Register("auth.shadow", New)
|
||||
}
|
||||
|
|
40
internal/authz/lookup.go
Normal file
40
internal/authz/lookup.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package authz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/address"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
func AuthorizeEmailUse(ctx context.Context, username, addr string, mapping module.Table) (bool, error) {
|
||||
_, domain, err := address.Split(addr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("authz: %w", err)
|
||||
}
|
||||
|
||||
var validEmails []string
|
||||
if multi, ok := mapping.(module.MultiTable); ok {
|
||||
validEmails, err = multi.LookupMulti(ctx, username)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("authz: %w", err)
|
||||
}
|
||||
} else {
|
||||
validEmail, ok, err := mapping.Lookup(ctx, username)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("authz: %w", err)
|
||||
}
|
||||
if ok {
|
||||
validEmails = []string{validEmail}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ent := range validEmails {
|
||||
if ent == domain || ent == "*" || ent == addr {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
23
internal/authz/normalization.go
Normal file
23
internal/authz/normalization.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package authz
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/address"
|
||||
"golang.org/x/text/secure/precis"
|
||||
)
|
||||
|
||||
// NormalizeFuncs defines configurable normalization functions to be used
|
||||
// in authentication and authorization routines.
|
||||
var NormalizeFuncs = map[string]func(string) (string, error){
|
||||
"precis_casefold_email": address.PRECISFold,
|
||||
"precis_casefold": precis.UsernameCaseMapped.CompareKey,
|
||||
"precis_email": address.PRECIS,
|
||||
"precis": precis.UsernameCasePreserved.CompareKey,
|
||||
"casefold": func(s string) (string, error) {
|
||||
return strings.ToLower(s), nil
|
||||
},
|
||||
"noop": func(s string) (string, error) {
|
||||
return s, nil
|
||||
},
|
||||
}
|
311
internal/check/authorize_sender/authorize_sender.go
Normal file
311
internal/check/authorize_sender/authorize_sender.go
Normal file
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
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 authorize_sender
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
|
||||
"github.com/emersion/go-message/textproto"
|
||||
"github.com/foxcpp/maddy/framework/buffer"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/foxcpp/maddy/internal/authz"
|
||||
"github.com/foxcpp/maddy/internal/table"
|
||||
"github.com/foxcpp/maddy/internal/target"
|
||||
)
|
||||
|
||||
const modName = "check.authorize_sender"
|
||||
|
||||
type Check struct {
|
||||
instName string
|
||||
log log.Logger
|
||||
|
||||
checkHeader bool
|
||||
emailPrepare module.Table
|
||||
userToEmail module.Table
|
||||
|
||||
unauthAction modconfig.FailAction
|
||||
noMatchAction modconfig.FailAction
|
||||
errAction modconfig.FailAction
|
||||
|
||||
fromNorm func(string) (string, error)
|
||||
authNorm func(string) (string, error)
|
||||
}
|
||||
|
||||
func New(_, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
return &Check{
|
||||
instName: instName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Check) Name() string {
|
||||
return modName
|
||||
}
|
||||
|
||||
func (c *Check) InstanceName() string {
|
||||
return c.instName
|
||||
}
|
||||
|
||||
func (c *Check) Init(cfg *config.Map) error {
|
||||
cfg.Bool("debug", true, false, &c.log.Debug)
|
||||
|
||||
cfg.Bool("check_header", false, true, &c.checkHeader)
|
||||
|
||||
cfg.Custom("prepare_email", false, false, func() (interface{}, error) {
|
||||
return &table.Identity{}, nil
|
||||
}, modconfig.TableDirective, &c.emailPrepare)
|
||||
cfg.Custom("user_to_email", false, false, func() (interface{}, error) {
|
||||
return &table.Identity{}, nil
|
||||
}, modconfig.TableDirective, &c.userToEmail)
|
||||
|
||||
cfg.Custom("unauth_action", false, false, func() (interface{}, error) {
|
||||
return modconfig.FailAction{Reject: true}, nil
|
||||
}, modconfig.FailActionDirective, &c.unauthAction)
|
||||
cfg.Custom("no_match_action", false, false, func() (interface{}, error) {
|
||||
return modconfig.FailAction{Reject: true}, nil
|
||||
}, modconfig.FailActionDirective, &c.noMatchAction)
|
||||
cfg.Custom("err_action", false, false, func() (interface{}, error) {
|
||||
return modconfig.FailAction{Reject: true}, nil
|
||||
}, modconfig.FailActionDirective, &c.errAction)
|
||||
|
||||
var (
|
||||
authNormalize string
|
||||
fromNormalize string
|
||||
ok bool
|
||||
)
|
||||
cfg.String("auth_normalize", false, false,
|
||||
"precis_casefold_email", &authNormalize)
|
||||
cfg.String("from_normalize", false, false,
|
||||
"precis_casefold_email", &fromNormalize)
|
||||
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.authNorm, ok = authz.NormalizeFuncs[authNormalize]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v: unknown normalization function: %v", modName, authNormalize)
|
||||
}
|
||||
c.fromNorm, ok = authz.NormalizeFuncs[fromNormalize]
|
||||
if !ok {
|
||||
return fmt.Errorf("%v: unknown normalization function: %v", modName, fromNormalize)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type state struct {
|
||||
c *Check
|
||||
msgMeta *module.MsgMetadata
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (c *Check) CheckStateForMsg(_ context.Context, msgMeta *module.MsgMetadata) (module.CheckState, error) {
|
||||
return &state{
|
||||
c: c,
|
||||
msgMeta: msgMeta,
|
||||
log: target.DeliveryLogger(c.log, msgMeta),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *state) authzSender(ctx context.Context, authName, email string) module.CheckResult {
|
||||
if authName == "" {
|
||||
return s.c.unauthAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 530,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
|
||||
Message: "Authentication required",
|
||||
CheckName: modName,
|
||||
}})
|
||||
}
|
||||
|
||||
fromEmailNorm, err := s.c.fromNorm(email)
|
||||
if err != nil {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 553,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 1, 7},
|
||||
Message: "Unable to normalize sender address",
|
||||
CheckName: modName,
|
||||
Err: err,
|
||||
}})
|
||||
}
|
||||
authNameNorm, err := s.c.authNorm(authName)
|
||||
if err != nil {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 535,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 8},
|
||||
Message: "Unable to normalize authorization username",
|
||||
CheckName: modName,
|
||||
}})
|
||||
}
|
||||
|
||||
s.log.DebugMsg("normalized names", "from", fromEmailNorm, "auth", authNameNorm)
|
||||
|
||||
preparedEmail, ok, err := s.c.emailPrepare.Lookup(ctx, fromEmailNorm)
|
||||
if err != nil {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 454,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 7, 0},
|
||||
Message: "Internal error during policy check",
|
||||
CheckName: modName,
|
||||
Err: err,
|
||||
}})
|
||||
}
|
||||
if !ok {
|
||||
preparedEmail = fromEmailNorm
|
||||
}
|
||||
|
||||
ok, err = authz.AuthorizeEmailUse(ctx, authNameNorm, preparedEmail, s.c.userToEmail)
|
||||
if err != nil {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 454,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 7, 0},
|
||||
Message: "Internal error during policy check",
|
||||
CheckName: modName,
|
||||
Err: err,
|
||||
}})
|
||||
}
|
||||
if !ok {
|
||||
return s.c.noMatchAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 553,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
|
||||
Message: "Unauthorized use of sender address",
|
||||
CheckName: modName,
|
||||
}})
|
||||
}
|
||||
|
||||
return module.CheckResult{}
|
||||
}
|
||||
|
||||
func (s *state) CheckConnection(_ context.Context) module.CheckResult {
|
||||
return module.CheckResult{}
|
||||
}
|
||||
|
||||
func (s *state) CheckSender(ctx context.Context, fromEmail string) module.CheckResult {
|
||||
if s.msgMeta.Conn == nil {
|
||||
s.log.Msg("skipping locally generated message")
|
||||
return module.CheckResult{}
|
||||
}
|
||||
authName := s.msgMeta.Conn.AuthUser
|
||||
|
||||
return s.authzSender(ctx, authName, fromEmail)
|
||||
}
|
||||
|
||||
func (s *state) CheckRcpt(_ context.Context, _ string) module.CheckResult {
|
||||
return module.CheckResult{}
|
||||
}
|
||||
|
||||
func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, _ buffer.Buffer) module.CheckResult {
|
||||
if !s.c.checkHeader {
|
||||
return module.CheckResult{}
|
||||
}
|
||||
if s.msgMeta.Conn == nil {
|
||||
s.log.Msg("skipping locally generated message")
|
||||
return module.CheckResult{}
|
||||
}
|
||||
authName := s.msgMeta.Conn.AuthUser
|
||||
|
||||
fromHdr := hdr.Get("From")
|
||||
if fromHdr == "" {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 550,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
|
||||
Message: "Missing From header",
|
||||
CheckName: modName,
|
||||
}})
|
||||
}
|
||||
list, err := mail.ParseAddressList(fromHdr)
|
||||
if err != nil || len(list) == 0 {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 550,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
|
||||
Message: "Malformed From header",
|
||||
CheckName: modName,
|
||||
Err: err,
|
||||
}})
|
||||
}
|
||||
fromEmail := list[0].Address
|
||||
if len(list) > 1 {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 550,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
|
||||
Message: "Multiple From addresses are not allowed",
|
||||
CheckName: modName,
|
||||
Err: err,
|
||||
}})
|
||||
}
|
||||
|
||||
var senderAddr string
|
||||
if senderHdr := hdr.Get("Sender"); senderHdr != "" {
|
||||
sender, err := mail.ParseAddress(senderHdr)
|
||||
if err != nil {
|
||||
return s.c.errAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 550,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
|
||||
Message: "Malformed Sender header",
|
||||
CheckName: modName,
|
||||
Err: err,
|
||||
}})
|
||||
}
|
||||
senderAddr = sender.Address
|
||||
}
|
||||
|
||||
res := s.authzSender(ctx, authName, fromEmail)
|
||||
if res.Reason == nil {
|
||||
return res
|
||||
}
|
||||
|
||||
if senderAddr != "" && senderAddr != fromEmail {
|
||||
res = s.authzSender(ctx, authName, senderAddr)
|
||||
if res.Reason == nil {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
// Neither matched.
|
||||
return s.c.noMatchAction.Apply(module.CheckResult{
|
||||
Reason: &exterrors.SMTPError{
|
||||
Code: 553,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 7, 0},
|
||||
Message: "Unauthorized use of sender address",
|
||||
CheckName: modName,
|
||||
}})
|
||||
}
|
||||
|
||||
func (s *state) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.Register(modName, New)
|
||||
}
|
|
@ -70,10 +70,10 @@ func New(modName, instName string, aliases, inlineArgs []string) (module.Module,
|
|||
c := &Check{
|
||||
instName: instName,
|
||||
actions: map[int]modconfig.FailAction{
|
||||
1: modconfig.FailAction{
|
||||
1: {
|
||||
Reject: true,
|
||||
},
|
||||
2: modconfig.FailAction{
|
||||
2: {
|
||||
Quarantine: true,
|
||||
},
|
||||
},
|
||||
|
@ -397,6 +397,5 @@ func (s *state) Close() error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("command", "check.command", New)
|
||||
module.Register(modName, New)
|
||||
}
|
||||
|
|
|
@ -268,6 +268,5 @@ func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module.MsgMetadat
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("verify_dkim", "check.dkim", New)
|
||||
module.Register("check.dkim", New)
|
||||
}
|
||||
|
|
|
@ -158,6 +158,8 @@ func requireMXRecord(ctx check.StatelessCheckContext, mailFrom string) module.Ch
|
|||
}
|
||||
|
||||
func requireMatchingEHLO(ctx check.StatelessCheckContext) module.CheckResult {
|
||||
ctx.Logger.Printf("require_matching_echo is deprecated and will be removed in the next release")
|
||||
|
||||
if ctx.MsgMeta.Conn == nil {
|
||||
ctx.Logger.Printf("locally-generated message, skipping")
|
||||
return module.CheckResult{}
|
||||
|
|
|
@ -44,7 +44,7 @@ func TestRequireMatchingRDNS(t *testing.T) {
|
|||
res := requireMatchingRDNS(check.StatelessCheckContext{
|
||||
Resolver: &mockdns.Resolver{
|
||||
Zones: map[string]mockdns.Zone{
|
||||
"4.3.2.1.in-addr.arpa.": mockdns.Zone{
|
||||
"4.3.2.1.in-addr.arpa.": {
|
||||
PTR: ptr,
|
||||
},
|
||||
},
|
||||
|
@ -85,7 +85,7 @@ func TestRequireMXRecord(t *testing.T) {
|
|||
res := requireMXRecord(check.StatelessCheckContext{
|
||||
Resolver: &mockdns.Resolver{
|
||||
Zones: map[string]mockdns.Zone{
|
||||
mxDomain + ".": mockdns.Zone{
|
||||
mxDomain + ".": {
|
||||
MX: mx,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -441,6 +441,5 @@ func (*state) Close() error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("dnsbl", "check.dnsbl", NewDNSBL)
|
||||
module.Register("check.dnsbl", NewDNSBL)
|
||||
}
|
||||
|
|
|
@ -445,6 +445,5 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("milter", "check.milter", New)
|
||||
module.Register(modName, New)
|
||||
}
|
||||
|
|
|
@ -363,6 +363,5 @@ func (s *state) Close() error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("rspamd", modName, New)
|
||||
module.Register(modName, New)
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ type state struct {
|
|||
log log.Logger
|
||||
}
|
||||
|
||||
func (c *Check) CheckStateForMsg(msgMeta *module.MsgMetadata) (module.CheckState, error) {
|
||||
func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module.MsgMetadata) (module.CheckState, error) {
|
||||
return &state{
|
||||
c: c,
|
||||
msgMeta: msgMeta,
|
||||
|
|
|
@ -406,6 +406,5 @@ func (s *state) Close() error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("apply_spf", "check.spf", New)
|
||||
module.Register(modName, New)
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// Policy present & identifiers align => DMARC 'pass'
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=none"},
|
||||
},
|
||||
}, "From: hello@example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -70,7 +70,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// No SPF check run => DMARC 'none', no action taken
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=reject"},
|
||||
},
|
||||
}, "From: hello@example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -79,7 +79,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// No DKIM check run => DMARC 'none', no action taken
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=reject"},
|
||||
},
|
||||
}, "From: hello@example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -89,7 +89,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Check org. domain and from domain, prefer from domain.
|
||||
// https://tools.ietf.org/html/rfc7489#section-6.6.3
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=none"},
|
||||
},
|
||||
}, "From: hello@sub.example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -97,7 +97,7 @@ func TestDMARC(t *testing.T) {
|
|||
&authres.SPFResult{Value: authres.ResultNone, From: "example.org", Helo: "mx.example.org"},
|
||||
}, PolicyNone, authres.ResultPass)
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.sub.example.org.": mockdns.Zone{
|
||||
"_dmarc.sub.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=none"},
|
||||
},
|
||||
}, "From: hello@sub.example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -105,10 +105,10 @@ func TestDMARC(t *testing.T) {
|
|||
&authres.SPFResult{Value: authres.ResultNone, From: "example.org", Helo: "mx.example.org"},
|
||||
}, PolicyNone, authres.ResultPass)
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.sub.example.org.": mockdns.Zone{
|
||||
"_dmarc.sub.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=none"},
|
||||
},
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"v=malformed"},
|
||||
},
|
||||
}, "From: hello@sub.example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -119,7 +119,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Non-DMARC records are ignored.
|
||||
// https://tools.ietf.org/html/rfc7489#section-6.6.3
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"ignore", "v=DMARC1; p=none"},
|
||||
},
|
||||
}, "From: hello@sub.example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -130,7 +130,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Multiple policies => no policy.
|
||||
// https://tools.ietf.org/html/rfc7489#section-6.6.3
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=reject", "v=DMARC1; p=none"},
|
||||
},
|
||||
}, "From: hello@sub.example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -140,7 +140,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// Malformed policy => no policy
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
TXT: []string{"v=aaaa"},
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
@ -151,7 +151,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Policy fetch error => DMARC 'permerror' but the message
|
||||
// is accepted.
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
Err: errors.New("the dns server is going insane"),
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
@ -162,7 +162,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Policy fetch error => DMARC 'temperror' but the message
|
||||
// is accepted ("fail closed")
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
Err: &net.DNSError{
|
||||
Err: "the dns server is going insane, temporary",
|
||||
IsTemporary: true,
|
||||
|
@ -178,7 +178,7 @@ func TestDMARC(t *testing.T) {
|
|||
// can be found in check/dmarc/evaluate_test.go. This test merely checks
|
||||
// that the correct action is taken based on the policy.
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
TXT: []string{"v=DMARC1; p=none"},
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
@ -188,7 +188,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// Misaligned From vs DKIM => DMARC 'fail', policy says to reject
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
TXT: []string{"v=DMARC1; p=reject"},
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
@ -199,7 +199,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Misaligned From vs DKIM => DMARC 'fail'
|
||||
// Subdomain policy requests no action, main domain policy says to reject.
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
TXT: []string{"v=DMARC1; sp=none; p=reject"},
|
||||
},
|
||||
}, "From: hello@sub.example.com\r\n\r\n", []authres.Result{
|
||||
|
@ -209,7 +209,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// Misaligned From vs DKIM => DMARC 'fail', policy says to quarantine.
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
TXT: []string{"v=DMARC1; p=quarantine"},
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
|
25
internal/libdns/alidns.go
Normal file
25
internal/libdns/alidns.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
//+build libdns_alidns libdns_all
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/alidns"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.alidns", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := alidns.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("key_id", false, false, "", &p.AccKeyID)
|
||||
c.String("key_secret", false, false, "", &p.AccKeySecret)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
24
internal/libdns/cloudflare.go
Normal file
24
internal/libdns/cloudflare.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
//+build libdns_cloudflare !libdns_separate
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/cloudflare"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.cloudflare", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := cloudflare.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("api_token", false, false, "", &p.APIToken)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
24
internal/libdns/digitalocean.go
Normal file
24
internal/libdns/digitalocean.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
//+build libdns_digitalocean !libdns_separate
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/digitalocean"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.digitalocean", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := digitalocean.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("api_token", false, false, "", &p.APIToken)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
24
internal/libdns/gandi.go
Normal file
24
internal/libdns/gandi.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
//+build libdns_gandi !libdns_separate
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/gandi"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.gandi", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := gandi.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("api_token", false, true, "", &p.APIToken)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
25
internal/libdns/googleclouddns.go
Normal file
25
internal/libdns/googleclouddns.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
//+build libdns_googleclouddns libdns_all
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/googleclouddns"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.googleclouddns", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := googleclouddns.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("project", false, true, "", &p.Project)
|
||||
c.String("service_account_json", false, false, "", &p.ServiceAccountJSON)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
24
internal/libdns/hetzner.go
Normal file
24
internal/libdns/hetzner.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
//+build libdns_hetzner !libdns_separate
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/hetzner"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.hetzner", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := hetzner.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("api_token", false, false, "", &p.AuthAPIToken)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
24
internal/libdns/leaseweb.go
Normal file
24
internal/libdns/leaseweb.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
//+build libdns_leaseweb libdns_all
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/leaseweb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.leaseweb", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := leaseweb.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("api_key", false, false, "", &p.APIKey)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
27
internal/libdns/metaname.go
Normal file
27
internal/libdns/metaname.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
//+build libdns_metaname libdns_all
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/metaname"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.metaname", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := metaname.Provider{
|
||||
Endpoint: "https://metaname.net/api/1.1",
|
||||
}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("api_key", false, false, "", &p.APIKey)
|
||||
c.String("account_ref", false, false, "", &p.AccountReference)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
27
internal/libdns/namedotcom.go
Normal file
27
internal/libdns/namedotcom.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
//+build libdns_namedotdom libdns_all
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/namedotcom"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.namedotcom", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := namedotcom.Provider{
|
||||
Server: "https://api.name.com",
|
||||
}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("user", false, false, "", &p.User)
|
||||
c.String("token", false, false, "", &p.Token)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
29
internal/libdns/provider_module.go
Normal file
29
internal/libdns/provider_module.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/libdns/libdns"
|
||||
)
|
||||
|
||||
type ProviderModule struct {
|
||||
libdns.RecordDeleter
|
||||
libdns.RecordAppender
|
||||
setConfig func(c *config.Map)
|
||||
|
||||
instName string
|
||||
modName string
|
||||
}
|
||||
|
||||
func (p *ProviderModule) Init(cfg *config.Map) error {
|
||||
p.setConfig(cfg)
|
||||
_, err := cfg.Process()
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *ProviderModule) Name() string {
|
||||
return p.modName
|
||||
}
|
||||
|
||||
func (p *ProviderModule) InstanceName() string {
|
||||
return p.instName
|
||||
}
|
25
internal/libdns/route53.go
Normal file
25
internal/libdns/route53.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
//+build libdns_route53 libdns_all
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/route53"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.route53", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := route53.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("secret_access_key", false, false, "", &p.SecretAccessKey)
|
||||
c.String("access_key_id", false, false, "", &p.AccessKeyId)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
24
internal/libdns/vultr.go
Normal file
24
internal/libdns/vultr.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
//+build libdns_vultr !libdns_separate
|
||||
|
||||
package libdns
|
||||
|
||||
import (
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/libdns/vultr"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module.Register("libdns.vultr", func(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
p := vultr.Provider{}
|
||||
return &ProviderModule{
|
||||
RecordDeleter: &p,
|
||||
RecordAppender: &p,
|
||||
setConfig: func(c *config.Map) {
|
||||
c.String("api_token", false, false, "", &p.APIToken)
|
||||
},
|
||||
instName: instName,
|
||||
modName: modName,
|
||||
}, nil
|
||||
})
|
||||
}
|
|
@ -383,6 +383,5 @@ func (s state) Close() error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("sign_dkim", "modify.dkim", New)
|
||||
module.Register("modify.dkim", New)
|
||||
}
|
||||
|
|
|
@ -76,14 +76,14 @@ func (r replaceAddr) ModStateForMsg(ctx context.Context, msgMeta *module.MsgMeta
|
|||
|
||||
func (r replaceAddr) RewriteSender(ctx context.Context, mailFrom string) (string, error) {
|
||||
if r.replaceSender {
|
||||
return r.rewrite(mailFrom)
|
||||
return r.rewrite(ctx, mailFrom)
|
||||
}
|
||||
return mailFrom, nil
|
||||
}
|
||||
|
||||
func (r replaceAddr) RewriteRcpt(ctx context.Context, rcptTo string) (string, error) {
|
||||
if r.replaceRcpt {
|
||||
return r.rewrite(rcptTo)
|
||||
return r.rewrite(ctx, rcptTo)
|
||||
}
|
||||
return rcptTo, nil
|
||||
}
|
||||
|
@ -96,13 +96,13 @@ func (r replaceAddr) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r replaceAddr) rewrite(val string) (string, error) {
|
||||
func (r replaceAddr) rewrite(ctx context.Context, val string) (string, error) {
|
||||
normAddr, err := address.ForLookup(val)
|
||||
if err != nil {
|
||||
return val, fmt.Errorf("malformed address: %v", err)
|
||||
}
|
||||
|
||||
replacement, ok, err := r.table.Lookup(normAddr)
|
||||
replacement, ok, err := r.table.Lookup(ctx, normAddr)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ func (r replaceAddr) rewrite(val string) (string, error) {
|
|||
|
||||
// mbox is already normalized, since it is a part of address.ForLookup
|
||||
// result.
|
||||
replacement, ok, err = r.table.Lookup(mbox)
|
||||
replacement, ok, err = r.table.Lookup(ctx, mbox)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
|
@ -141,7 +141,5 @@ func (r replaceAddr) rewrite(val string) (string, error) {
|
|||
|
||||
func init() {
|
||||
module.Register("modify.replace_sender", NewReplaceAddr)
|
||||
module.RegisterDeprecated("replace_sender", "modify.replace_sender", NewReplaceAddr)
|
||||
module.Register("modify.replace_rcpt", NewReplaceAddr)
|
||||
module.RegisterDeprecated("replace_rcpt", "modify.replace_rcpt", NewReplaceAddr)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package msgpipeline
|
|||
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
|
||||
"github.com/emersion/go-message/textproto"
|
||||
|
@ -178,6 +179,14 @@ func (cr *checkRunner) runAndMergeResults(states []module.CheckState, runner fun
|
|||
state := state
|
||||
data.wg.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
data.wg.Done()
|
||||
if err := recover(); err != nil {
|
||||
stack := debug.Stack()
|
||||
log.Printf("panic during check execution: %v\n%s", err, stack)
|
||||
}
|
||||
}()
|
||||
|
||||
subCheckRes := runner(state)
|
||||
|
||||
// We check the length because we don't want to take locks
|
||||
|
@ -213,8 +222,6 @@ func (cr *checkRunner) runAndMergeResults(states []module.CheckState, runner fun
|
|||
// purposes of deployment testing.
|
||||
cr.log.Error("no check action", subCheckRes.Reason)
|
||||
}
|
||||
|
||||
data.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// Policy present & identifiers align => DMARC 'pass'
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.org.": mockdns.Zone{
|
||||
"_dmarc.example.org.": {
|
||||
TXT: []string{"v=DMARC1; p=none"},
|
||||
},
|
||||
}, "From: hello@example.org\r\n\r\n", []authres.Result{
|
||||
|
@ -179,7 +179,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Policy fetch error => DMARC 'permerror' but the message
|
||||
// is accepted.
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
Err: errors.New("the dns server is going insane"),
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
@ -190,7 +190,7 @@ func TestDMARC(t *testing.T) {
|
|||
// Policy fetch error => DMARC 'temperror' but the message
|
||||
// is rejected ("fail closed")
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
Err: &net.DNSError{
|
||||
Err: "the dns server is going insane, temporary",
|
||||
IsTemporary: true,
|
||||
|
@ -203,7 +203,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// Misaligned From vs DKIM => DMARC 'fail', policy says to reject
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
TXT: []string{"v=DMARC1; p=reject"},
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
@ -213,7 +213,7 @@ func TestDMARC(t *testing.T) {
|
|||
|
||||
// Misaligned From vs DKIM => DMARC 'fail', policy says to quarantine.
|
||||
test(map[string]mockdns.Zone{
|
||||
"_dmarc.example.com.": mockdns.Zone{
|
||||
"_dmarc.example.com.": {
|
||||
TXT: []string{"v=DMARC1; p=quarantine"},
|
||||
},
|
||||
}, "From: hello@example.com\r\n\r\n", []authres.Result{
|
||||
|
|
|
@ -151,7 +151,7 @@ func (dd *msgpipelineDelivery) start(ctx context.Context, msgMeta *module.MsgMet
|
|||
return err
|
||||
}
|
||||
|
||||
sourceBlock, err := dd.srcBlockForAddr(mailFrom)
|
||||
sourceBlock, err := dd.srcBlockForAddr(ctx, mailFrom)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ func (dd *msgpipelineDelivery) initRunGlobalModifiers(ctx context.Context, msgMe
|
|||
return mailFrom, nil
|
||||
}
|
||||
|
||||
func (dd *msgpipelineDelivery) srcBlockForAddr(mailFrom string) (sourceBlock, error) {
|
||||
func (dd *msgpipelineDelivery) srcBlockForAddr(ctx context.Context, mailFrom string) (sourceBlock, error) {
|
||||
var cleanFrom = mailFrom
|
||||
if mailFrom != "" {
|
||||
var err error
|
||||
|
@ -209,7 +209,7 @@ func (dd *msgpipelineDelivery) srcBlockForAddr(mailFrom string) (sourceBlock, er
|
|||
}
|
||||
|
||||
for _, srcIn := range dd.d.sourceIn {
|
||||
_, ok, err := srcIn.t.Lookup(cleanFrom)
|
||||
_, ok, err := srcIn.t.Lookup(ctx, cleanFrom)
|
||||
if err != nil {
|
||||
dd.log.Error("source_in lookup failed", err, "key", cleanFrom)
|
||||
continue
|
||||
|
@ -306,7 +306,7 @@ func (dd *msgpipelineDelivery) AddRcpt(ctx context.Context, to string) error {
|
|||
})
|
||||
}
|
||||
|
||||
rcptBlock, err := dd.rcptBlockForAddr(to)
|
||||
rcptBlock, err := dd.rcptBlockForAddr(ctx, to)
|
||||
if err != nil {
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
@ -532,7 +532,7 @@ func (dd msgpipelineDelivery) Abort(ctx context.Context) error {
|
|||
return lastErr
|
||||
}
|
||||
|
||||
func (dd *msgpipelineDelivery) rcptBlockForAddr(rcptTo string) (*rcptBlock, error) {
|
||||
func (dd *msgpipelineDelivery) rcptBlockForAddr(ctx context.Context, rcptTo string) (*rcptBlock, error) {
|
||||
cleanRcpt, err := address.ForLookup(rcptTo)
|
||||
if err != nil {
|
||||
return nil, &exterrors.SMTPError{
|
||||
|
@ -544,7 +544,7 @@ func (dd *msgpipelineDelivery) rcptBlockForAddr(rcptTo string) (*rcptBlock, erro
|
|||
}
|
||||
|
||||
for _, rcptIn := range dd.sourceBlock.rcptIn {
|
||||
_, ok, err := rcptIn.t.Lookup(cleanRcpt)
|
||||
_, ok, err := rcptIn.t.Lookup(ctx, cleanRcpt)
|
||||
if err != nil {
|
||||
dd.log.Error("destination_in lookup failed", err, "key", cleanRcpt)
|
||||
continue
|
||||
|
|
|
@ -95,15 +95,15 @@ func TestMsgPipeline_SourceIn(t *testing.T) {
|
|||
d := MsgPipeline{
|
||||
msgpipelineCfg: msgpipelineCfg{
|
||||
sourceIn: []sourceIn{
|
||||
sourceIn{
|
||||
{
|
||||
t: testutils.Table{},
|
||||
block: sourceBlock{rejectErr: errors.New("non-matching block was used")},
|
||||
},
|
||||
sourceIn{
|
||||
{
|
||||
t: testutils.Table{Err: errors.New("this one will fail")},
|
||||
block: sourceBlock{rejectErr: errors.New("failing block was used")},
|
||||
},
|
||||
sourceIn{
|
||||
{
|
||||
t: testutils.Table{
|
||||
M: map[string]string{
|
||||
"specific@example.com": "",
|
||||
|
@ -594,7 +594,7 @@ func TestMsgPipeline_UnicodeNFC_Rcpt(t *testing.T) {
|
|||
testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"rcpt@E\u0301.EXAMPLE.com"})
|
||||
testutils.DoTestDelivery(t, &d, "sender@example.com", []string{"f@E\u0301.exAMPle.com"})
|
||||
if len(target.Messages) != 2 {
|
||||
t.Fatalf("wrong amount of messages received for target, want %d, got %d", 3, len(target.Messages))
|
||||
t.Fatalf("wrong amount of messages received for target, want %d, got %d", 2, len(target.Messages))
|
||||
}
|
||||
testutils.CheckTestMessage(t, &target, 0, "sender@example.com", []string{"rcpt@E\u0301.EXAMPLE.com"})
|
||||
testutils.CheckTestMessage(t, &target, 1, "sender@example.com", []string{"f@E\u0301.exAMPle.com"})
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"runtime/trace"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-message/textproto"
|
||||
"github.com/emersion/go-smtp"
|
||||
|
@ -52,6 +53,17 @@ type C struct {
|
|||
// DialContext by New.
|
||||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Timeout for most session commands (EHLO, MAIL, RCPT, DATA, STARTTLS).
|
||||
// Set to 5 mins by New.
|
||||
CommandTimeout time.Duration
|
||||
|
||||
// Timeout for the initial TCP connection establishment.
|
||||
ConnectTimeout time.Duration
|
||||
|
||||
// Timeout for the final dot. Set to 12 mins by New.
|
||||
// (see go-smtp source for explanation of used defaults).
|
||||
SubmissionTimeout time.Duration
|
||||
|
||||
// Hostname to sent in the EHLO/HELO command. Set to
|
||||
// 'localhost.localdomain' by New. Expected to be encoded in ACE form.
|
||||
Hostname string
|
||||
|
@ -76,6 +88,9 @@ type C struct {
|
|||
func New() *C {
|
||||
return &C{
|
||||
Dialer: (&net.Dialer{}).DialContext,
|
||||
ConnectTimeout: 5 * time.Minute,
|
||||
CommandTimeout: 5 * time.Minute,
|
||||
SubmissionTimeout: 12 * time.Minute,
|
||||
TLSConfig: &tls.Config{},
|
||||
Hostname: "localhost.localdomain",
|
||||
}
|
||||
|
@ -188,7 +203,10 @@ func (err TLSError) Unwrap() error {
|
|||
|
||||
func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint, starttls bool, tlsConfig *tls.Config) (didTLS bool, cl *smtp.Client, err error) {
|
||||
var conn net.Conn
|
||||
conn, err = c.Dialer(ctx, endp.Network(), endp.Address())
|
||||
|
||||
dialCtx, cancel := context.WithTimeout(ctx, c.ConnectTimeout)
|
||||
conn, err = c.Dialer(dialCtx, endp.Network(), endp.Address())
|
||||
cancel()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
@ -199,6 +217,7 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint,
|
|||
conn = tls.Client(conn, cfg)
|
||||
}
|
||||
|
||||
// This uses initial greeting timeout of 5 minutes (hardcoded).
|
||||
if lmtp {
|
||||
cl, err = smtp.NewClientLMTP(conn, endp.Host)
|
||||
} else {
|
||||
|
@ -209,6 +228,9 @@ func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config.Endpoint,
|
|||
return false, nil, err
|
||||
}
|
||||
|
||||
cl.CommandTimeout = c.CommandTimeout
|
||||
cl.SubmissionTimeout = c.SubmissionTimeout
|
||||
|
||||
// i18n: hostname is already expected to be in A-labels form.
|
||||
if err := cl.Hello(c.Hostname); err != nil {
|
||||
cl.Close()
|
||||
|
|
89
internal/storage/blob/fs/fs.go
Normal file
89
internal/storage/blob/fs/fs.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
// FSStore struct represents directory on FS used to store blobs.
|
||||
type FSStore struct {
|
||||
instName string
|
||||
root string
|
||||
}
|
||||
|
||||
func New(_, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
switch len(inlineArgs) {
|
||||
case 0:
|
||||
return &FSStore{instName: instName}, nil
|
||||
case 1:
|
||||
return &FSStore{instName: instName, root: inlineArgs[0]}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("storage.blob.fs: 1 or 0 arguments expected")
|
||||
}
|
||||
}
|
||||
|
||||
func (s FSStore) Name() string {
|
||||
return "storage.blob.fs"
|
||||
}
|
||||
|
||||
func (s FSStore) InstanceName() string {
|
||||
return s.instName
|
||||
}
|
||||
|
||||
func (s *FSStore) Init(cfg *config.Map) error {
|
||||
cfg.String("root", false, false, s.root, &s.root)
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.root == "" {
|
||||
return config.NodeErr(cfg.Block, "storage.blob.fs: directory not set")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(s.root, os.ModeDir|os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FSStore) Open(key string) (io.ReadCloser, error) {
|
||||
f, err := os.Open(filepath.Join(s.root, key))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, module.ErrNoSuchBlob
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (s *FSStore) Create(key string) (module.Blob, error) {
|
||||
f, err := os.Create(filepath.Join(s.root, key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (s *FSStore) Delete(keys []string) error {
|
||||
for _, key := range keys {
|
||||
if err := os.Remove(filepath.Join(s.root, key)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
var _ module.BlobStore = &FSStore{}
|
||||
module.Register(FSStore{}.Name(), New)
|
||||
}
|
19
internal/storage/blob/fs/fs_test.go
Normal file
19
internal/storage/blob/fs/fs_test.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/foxcpp/maddy/internal/storage/blob"
|
||||
"github.com/foxcpp/maddy/internal/testutils"
|
||||
)
|
||||
|
||||
func TestFS(t *testing.T) {
|
||||
blob.TestStore(t, func() module.BlobStore {
|
||||
dir := testutils.Dir(t)
|
||||
return &FSStore{instName: "test", root: dir}
|
||||
}, func(store module.BlobStore) {
|
||||
os.RemoveAll(store.(*FSStore).root)
|
||||
})
|
||||
}
|
144
internal/storage/blob/s3/s3.go
Normal file
144
internal/storage/blob/s3/s3.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package s3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
const modName = "storage.blob.s3"
|
||||
|
||||
type Store struct {
|
||||
instName string
|
||||
log log.Logger
|
||||
|
||||
endpoint string
|
||||
cl *minio.Client
|
||||
|
||||
bucketName string
|
||||
objectPrefix string
|
||||
}
|
||||
|
||||
func New(_, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
if len(inlineArgs) != 0 {
|
||||
return nil, fmt.Errorf("%s: expected 0 arguments", modName)
|
||||
}
|
||||
|
||||
return &Store{
|
||||
instName: instName,
|
||||
log: log.Logger{Name: modName},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) Init(cfg *config.Map) error {
|
||||
var (
|
||||
secure bool
|
||||
accessKeyID string
|
||||
secretAccessKey string
|
||||
location string
|
||||
)
|
||||
cfg.String("endpoint", false, true, "", &s.endpoint)
|
||||
cfg.Bool("secure", false, true, &secure)
|
||||
cfg.String("access_key", false, true, "", &accessKeyID)
|
||||
cfg.String("secret_key", false, true, "", &secretAccessKey)
|
||||
cfg.String("bucket", false, true, "", &s.bucketName)
|
||||
cfg.String("region", false, false, "", &location)
|
||||
cfg.String("object_prefix", false, false, "", &s.objectPrefix)
|
||||
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.endpoint == "" {
|
||||
return fmt.Errorf("%s: endpoint not set", modName)
|
||||
}
|
||||
|
||||
cl, err := minio.New(s.endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
|
||||
Secure: secure,
|
||||
Region: location,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", modName, err)
|
||||
}
|
||||
|
||||
s.cl = cl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Name() string {
|
||||
return modName
|
||||
}
|
||||
|
||||
func (s *Store) InstanceName() string {
|
||||
return s.instName
|
||||
}
|
||||
|
||||
type s3blob struct {
|
||||
pw *io.PipeWriter
|
||||
didSync bool
|
||||
errCh chan error
|
||||
}
|
||||
|
||||
func (b *s3blob) Sync() error {
|
||||
// We do this in Sync instead of Close because
|
||||
// backend may not actually check the error of Close.
|
||||
|
||||
// The problematic restriction is that Sync can now be called
|
||||
// only once.
|
||||
b.pw.Close()
|
||||
return <-b.errCh
|
||||
}
|
||||
|
||||
func (b *s3blob) Write(p []byte) (n int, err error) {
|
||||
return b.pw.Write(p)
|
||||
}
|
||||
|
||||
func (b *s3blob) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Create(key string) (module.Blob, error) {
|
||||
pr, pw := io.Pipe()
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
_, err := s.cl.PutObject(context.TODO(), s.bucketName, s.objectPrefix+key, pr, -1, minio.PutObjectOptions{})
|
||||
errCh <- err
|
||||
}()
|
||||
|
||||
return &s3blob{pw: pw, errCh: errCh}, nil
|
||||
}
|
||||
|
||||
func (s *Store) Open(key string) (io.ReadCloser, error) {
|
||||
obj, err := s.cl.GetObject(context.TODO(), s.bucketName, s.objectPrefix+key, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
resp := minio.ToErrorResponse(err)
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return nil, module.ErrNoSuchBlob
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func (s *Store) Delete(keys []string) error {
|
||||
var lastErr error
|
||||
for _, k := range keys {
|
||||
lastErr = s.cl.RemoveObject(context.TODO(), s.bucketName, s.objectPrefix+k, minio.RemoveObjectOptions{})
|
||||
if lastErr != nil {
|
||||
s.log.Error("failed to delete object", lastErr, s.objectPrefix+k)
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.Register(modName, New)
|
||||
}
|
71
internal/storage/blob/s3/s3_test.go
Normal file
71
internal/storage/blob/s3/s3_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package s3
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/foxcpp/maddy/internal/storage/blob"
|
||||
"github.com/johannesboyne/gofakes3"
|
||||
"github.com/johannesboyne/gofakes3/backend/s3mem"
|
||||
)
|
||||
|
||||
func TestFS(t *testing.T) {
|
||||
var (
|
||||
backend gofakes3.Backend
|
||||
faker *gofakes3.GoFakeS3
|
||||
ts *httptest.Server
|
||||
)
|
||||
|
||||
blob.TestStore(t, func() module.BlobStore {
|
||||
backend = s3mem.New()
|
||||
faker = gofakes3.New(backend)
|
||||
ts = httptest.NewServer(faker.Server())
|
||||
|
||||
if err := backend.CreateBucket("maddy-test"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
st := &Store{instName: "test"}
|
||||
err := st.Init(config.NewMap(map[string]interface{}{}, config.Node{
|
||||
Children: []config.Node{
|
||||
{
|
||||
Name: "endpoint",
|
||||
Args: []string{ts.Listener.Addr().String()},
|
||||
},
|
||||
{
|
||||
Name: "secure",
|
||||
Args: []string{"false"},
|
||||
},
|
||||
{
|
||||
Name: "access_key",
|
||||
Args: []string{"access-key"},
|
||||
},
|
||||
{
|
||||
Name: "secret_key",
|
||||
Args: []string{"secret-key"},
|
||||
},
|
||||
{
|
||||
Name: "bucket",
|
||||
Args: []string{"maddy-test"},
|
||||
},
|
||||
},
|
||||
}))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return st
|
||||
}, func(store module.BlobStore) {
|
||||
ts.Close()
|
||||
|
||||
backend = s3mem.New()
|
||||
faker = gofakes3.New(backend)
|
||||
ts = httptest.NewServer(faker.Server())
|
||||
})
|
||||
|
||||
if ts != nil {
|
||||
ts.Close()
|
||||
}
|
||||
}
|
60
internal/storage/blob/test_blob.go
Normal file
60
internal/storage/blob/test_blob.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
//+build cgo,!no_sqlite3
|
||||
|
||||
package blob
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
backendtests "github.com/foxcpp/go-imap-backend-tests"
|
||||
imapsql "github.com/foxcpp/go-imap-sql"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
imapsql2 "github.com/foxcpp/maddy/internal/storage/imapsql"
|
||||
"github.com/foxcpp/maddy/internal/testutils"
|
||||
)
|
||||
|
||||
type testBack struct {
|
||||
backendtests.Backend
|
||||
ExtStore module.BlobStore
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T, newStore func() module.BlobStore, cleanStore func(module.BlobStore)) {
|
||||
// We use go-imap-sql backend and run a subset of
|
||||
// go-imap-backend-tests related to loading and saving messages.
|
||||
//
|
||||
// In the future we should probably switch to using a memory
|
||||
// backend for this.
|
||||
|
||||
backendtests.Whitelist = []string{
|
||||
t.Name() + "/Mailbox_CreateMessage",
|
||||
t.Name() + "/Mailbox_ListMessages_Body",
|
||||
t.Name() + "/Mailbox_CopyMessages",
|
||||
t.Name() + "/Mailbox_Expunge",
|
||||
t.Name() + "/Mailbox_MoveMessages",
|
||||
}
|
||||
|
||||
initBackend := func() backendtests.Backend {
|
||||
randSrc := rand.NewSource(0)
|
||||
prng := rand.New(randSrc)
|
||||
store := newStore()
|
||||
|
||||
b, err := imapsql.New("sqlite3", ":memory:",
|
||||
imapsql2.ExtBlobStore{Base: store}, imapsql.Opts{
|
||||
LazyUpdatesInit: true,
|
||||
PRNG: prng,
|
||||
Log: testutils.Logger(t, "imapsql"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return testBack{Backend: b, ExtStore: store}
|
||||
}
|
||||
cleanBackend := func(bi backendtests.Backend) {
|
||||
b := bi.(testBack)
|
||||
b.Backend.(*imapsql.Backend).Close()
|
||||
cleanStore(b.ExtStore)
|
||||
}
|
||||
|
||||
backendtests.RunTests(t, initBackend, cleanBackend)
|
||||
}
|
13
internal/storage/blob/test_blob_nosqlite.go
Normal file
13
internal/storage/blob/test_blob_nosqlite.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
//+build !cgo no_sqlite3
|
||||
|
||||
package blob
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
func TestStore(t *testing.T, newStore func() module.BlobStore, cleanStore func(module.BlobStore)) {
|
||||
t.Skip("storage.blob tests require CGo and sqlite3")
|
||||
}
|
162
internal/storage/imapsql/delivery.go
Normal file
162
internal/storage/imapsql/delivery.go
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
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 imapsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/trace"
|
||||
|
||||
specialuse "github.com/emersion/go-imap-specialuse"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-message/textproto"
|
||||
imapsql "github.com/foxcpp/go-imap-sql"
|
||||
"github.com/foxcpp/maddy/framework/buffer"
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/foxcpp/maddy/internal/target"
|
||||
)
|
||||
|
||||
type delivery struct {
|
||||
store *Storage
|
||||
msgMeta *module.MsgMetadata
|
||||
d imapsql.Delivery
|
||||
mailFrom string
|
||||
|
||||
addedRcpts map[string]struct{}
|
||||
}
|
||||
|
||||
func (d *delivery) String() string {
|
||||
return d.store.Name() + ":" + d.store.InstanceName()
|
||||
}
|
||||
|
||||
func userDoesNotExist(actual error) error {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 501,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 1, 1},
|
||||
Message: "User does not exist",
|
||||
TargetName: "imapsql",
|
||||
Err: actual,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *delivery) AddRcpt(ctx context.Context, rcptTo string) error {
|
||||
defer trace.StartRegion(ctx, "sql/AddRcpt").End()
|
||||
|
||||
accountName, err := d.store.deliveryNormalize(ctx, rcptTo)
|
||||
if err != nil {
|
||||
return userDoesNotExist(err)
|
||||
}
|
||||
|
||||
if _, ok := d.addedRcpts[accountName]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This header is added to the message only for that recipient.
|
||||
// go-imap-sql does certain optimizations to store the message
|
||||
// with small amount of per-recipient data in a efficient way.
|
||||
userHeader := textproto.Header{}
|
||||
userHeader.Add("Delivered-To", accountName)
|
||||
|
||||
if err := d.d.AddRcpt(accountName, userHeader); err != nil {
|
||||
if err == imapsql.ErrUserDoesntExists || err == backend.ErrNoSuchMailbox {
|
||||
return userDoesNotExist(err)
|
||||
}
|
||||
if _, ok := err.(imapsql.SerializationError); ok {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 453,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 3, 2},
|
||||
Message: "Internal server error, try again later",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
d.addedRcpts[accountName] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *delivery) Body(ctx context.Context, header textproto.Header, body buffer.Buffer) error {
|
||||
defer trace.StartRegion(ctx, "sql/Body").End()
|
||||
|
||||
if !d.msgMeta.Quarantine && d.store.filters != nil {
|
||||
for rcpt := range d.addedRcpts {
|
||||
folder, flags, err := d.store.filters.IMAPFilter(rcpt, d.msgMeta, header, body)
|
||||
if err != nil {
|
||||
d.store.Log.Error("IMAPFilter failed", err, "rcpt", rcpt)
|
||||
continue
|
||||
}
|
||||
d.d.UserMailbox(rcpt, folder, flags)
|
||||
}
|
||||
}
|
||||
|
||||
if d.msgMeta.Quarantine {
|
||||
if err := d.d.SpecialMailbox(specialuse.Junk, d.store.junkMbox); err != nil {
|
||||
if _, ok := err.(imapsql.SerializationError); ok {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 453,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 3, 2},
|
||||
Message: "Storage access serialiation problem, try again later",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
header = header.Copy()
|
||||
header.Add("Return-Path", "<"+target.SanitizeForHeader(d.mailFrom)+">")
|
||||
err := d.d.BodyParsed(header, body.Len(), body)
|
||||
if _, ok := err.(imapsql.SerializationError); ok {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 453,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 3, 2},
|
||||
Message: "Storage access serialiation problem, try again later",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *delivery) Abort(ctx context.Context) error {
|
||||
defer trace.StartRegion(ctx, "sql/Abort").End()
|
||||
|
||||
return d.d.Abort()
|
||||
}
|
||||
|
||||
func (d *delivery) Commit(ctx context.Context) error {
|
||||
defer trace.StartRegion(ctx, "sql/Commit").End()
|
||||
|
||||
return d.d.Commit()
|
||||
}
|
||||
|
||||
func (store *Storage) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) {
|
||||
defer trace.StartRegion(ctx, "sql/Start").End()
|
||||
|
||||
return &delivery{
|
||||
store: store,
|
||||
msgMeta: msgMeta,
|
||||
mailFrom: mailFrom,
|
||||
d: store.Back.NewDelivery(),
|
||||
addedRcpts: map[string]struct{}{},
|
||||
}, nil
|
||||
}
|
67
internal/storage/imapsql/external_blob_store.go
Normal file
67
internal/storage/imapsql/external_blob_store.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package imapsql
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
imapsql "github.com/foxcpp/go-imap-sql"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
type ExtBlob struct {
|
||||
io.ReadCloser
|
||||
}
|
||||
|
||||
func (e ExtBlob) Sync() error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (e ExtBlob) Write(p []byte) (n int, err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type WriteExtBlob struct {
|
||||
module.Blob
|
||||
}
|
||||
|
||||
func (w WriteExtBlob) Read(p []byte) (n int, err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type ExtBlobStore struct {
|
||||
Base module.BlobStore
|
||||
}
|
||||
|
||||
func (e ExtBlobStore) Create(key string) (imapsql.ExtStoreObj, error) {
|
||||
blob, err := e.Base.Create(key)
|
||||
if err != nil {
|
||||
return nil, imapsql.ExternalError{
|
||||
NonExistent: err == module.ErrNoSuchBlob,
|
||||
Key: key,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return WriteExtBlob{Blob: blob}, nil
|
||||
}
|
||||
|
||||
func (e ExtBlobStore) Open(key string) (imapsql.ExtStoreObj, error) {
|
||||
blob, err := e.Base.Open(key)
|
||||
if err != nil {
|
||||
return nil, imapsql.ExternalError{
|
||||
NonExistent: err == module.ErrNoSuchBlob,
|
||||
Key: key,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return ExtBlob{ReadCloser: blob}, nil
|
||||
}
|
||||
|
||||
func (e ExtBlobStore) Delete(keys []string) error {
|
||||
err := e.Base.Delete(keys)
|
||||
if err != nil {
|
||||
return imapsql.ExternalError{
|
||||
Key: "",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -31,29 +31,22 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime/trace"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
sortthread "github.com/emersion/go-imap-sortthread"
|
||||
specialuse "github.com/emersion/go-imap-specialuse"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-message/textproto"
|
||||
imapsql "github.com/foxcpp/go-imap-sql"
|
||||
"github.com/foxcpp/maddy/framework/address"
|
||||
"github.com/foxcpp/maddy/framework/buffer"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
"github.com/foxcpp/maddy/framework/dns"
|
||||
"github.com/foxcpp/maddy/framework/exterrors"
|
||||
"github.com/foxcpp/maddy/framework/log"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
"github.com/foxcpp/maddy/internal/target"
|
||||
"github.com/foxcpp/maddy/internal/authz"
|
||||
"github.com/foxcpp/maddy/internal/updatepipe"
|
||||
"golang.org/x/text/secure/precis"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
|
@ -76,142 +69,15 @@ type Storage struct {
|
|||
updPushStop chan struct{}
|
||||
|
||||
filters module.IMAPFilter
|
||||
}
|
||||
|
||||
type delivery struct {
|
||||
store *Storage
|
||||
msgMeta *module.MsgMetadata
|
||||
d imapsql.Delivery
|
||||
mailFrom string
|
||||
|
||||
addedRcpts map[string]struct{}
|
||||
}
|
||||
|
||||
func (d *delivery) String() string {
|
||||
return d.store.Name() + ":" + d.store.InstanceName()
|
||||
}
|
||||
|
||||
func (d *delivery) AddRcpt(ctx context.Context, rcptTo string) error {
|
||||
defer trace.StartRegion(ctx, "sql/AddRcpt").End()
|
||||
|
||||
accountName, err := prepareUsername(rcptTo)
|
||||
if err != nil {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 501,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 1, 1},
|
||||
Message: "User does not exist",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
accountName = strings.ToLower(accountName)
|
||||
if _, ok := d.addedRcpts[accountName]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This header is added to the message only for that recipient.
|
||||
// go-imap-sql does certain optimizations to store the message
|
||||
// with small amount of per-recipient data in a efficient way.
|
||||
userHeader := textproto.Header{}
|
||||
userHeader.Add("Delivered-To", accountName)
|
||||
|
||||
if err := d.d.AddRcpt(accountName, userHeader); err != nil {
|
||||
if err == imapsql.ErrUserDoesntExists || err == backend.ErrNoSuchMailbox {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 550,
|
||||
EnhancedCode: exterrors.EnhancedCode{5, 1, 1},
|
||||
Message: "User does not exist",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
if _, ok := err.(imapsql.SerializationError); ok {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 453,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 3, 2},
|
||||
Message: "Storage access serialiation problem, try again later",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
d.addedRcpts[accountName] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *delivery) Body(ctx context.Context, header textproto.Header, body buffer.Buffer) error {
|
||||
defer trace.StartRegion(ctx, "sql/Body").End()
|
||||
|
||||
if !d.msgMeta.Quarantine && d.store.filters != nil {
|
||||
for rcpt := range d.addedRcpts {
|
||||
folder, flags, err := d.store.filters.IMAPFilter(rcpt, d.msgMeta, header, body)
|
||||
if err != nil {
|
||||
d.store.Log.Error("IMAPFilter failed", err, "rcpt", rcpt)
|
||||
continue
|
||||
}
|
||||
d.d.UserMailbox(rcpt, folder, flags)
|
||||
}
|
||||
}
|
||||
|
||||
if d.msgMeta.Quarantine {
|
||||
if err := d.d.SpecialMailbox(specialuse.Junk, d.store.junkMbox); err != nil {
|
||||
if _, ok := err.(imapsql.SerializationError); ok {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 453,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 3, 2},
|
||||
Message: "Storage access serialiation problem, try again later",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
header = header.Copy()
|
||||
header.Add("Return-Path", "<"+target.SanitizeForHeader(d.mailFrom)+">")
|
||||
err := d.d.BodyParsed(header, body.Len(), body)
|
||||
if _, ok := err.(imapsql.SerializationError); ok {
|
||||
return &exterrors.SMTPError{
|
||||
Code: 453,
|
||||
EnhancedCode: exterrors.EnhancedCode{4, 3, 2},
|
||||
Message: "Storage access serialiation problem, try again later",
|
||||
TargetName: "imapsql",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *delivery) Abort(ctx context.Context) error {
|
||||
defer trace.StartRegion(ctx, "sql/Abort").End()
|
||||
|
||||
return d.d.Abort()
|
||||
}
|
||||
|
||||
func (d *delivery) Commit(ctx context.Context) error {
|
||||
defer trace.StartRegion(ctx, "sql/Commit").End()
|
||||
|
||||
return d.d.Commit()
|
||||
}
|
||||
|
||||
func (store *Storage) Start(ctx context.Context, msgMeta *module.MsgMetadata, mailFrom string) (module.Delivery, error) {
|
||||
defer trace.StartRegion(ctx, "sql/Start").End()
|
||||
|
||||
return &delivery{
|
||||
store: store,
|
||||
msgMeta: msgMeta,
|
||||
mailFrom: mailFrom,
|
||||
d: store.Back.NewDelivery(),
|
||||
addedRcpts: map[string]struct{}{},
|
||||
}, nil
|
||||
deliveryMap module.Table
|
||||
deliveryNormalize func(context.Context, string) (string, error)
|
||||
authMap module.Table
|
||||
authNormalize func(context.Context, string) (string, error)
|
||||
}
|
||||
|
||||
func (store *Storage) Name() string {
|
||||
return "sql"
|
||||
return "imapsql"
|
||||
}
|
||||
|
||||
func (store *Storage) InstanceName() string {
|
||||
|
@ -239,9 +105,12 @@ func (store *Storage) Init(cfg *config.Map) error {
|
|||
var (
|
||||
driver string
|
||||
dsn []string
|
||||
fsstoreLocation string
|
||||
appendlimitVal = -1
|
||||
compression []string
|
||||
authNormalize string
|
||||
deliveryNormalize string
|
||||
|
||||
blobStore module.BlobStore
|
||||
)
|
||||
|
||||
opts := imapsql.Opts{
|
||||
|
@ -251,14 +120,22 @@ func (store *Storage) Init(cfg *config.Map) error {
|
|||
}
|
||||
cfg.String("driver", false, false, store.driver, &driver)
|
||||
cfg.StringList("dsn", false, false, store.dsn, &dsn)
|
||||
cfg.Custom("fsstore", false, false, func() (interface{}, error) {
|
||||
return "messages", nil
|
||||
cfg.Callback("fsstore", func(m *config.Map, node config.Node) error {
|
||||
store.Log.Msg("'fsstore' directive is deprecated, use 'msg_store fs' instead")
|
||||
return modconfig.ModuleFromNode("storage.blob", append([]string{"fs"}, node.Args...),
|
||||
node, m.Globals, &blobStore)
|
||||
})
|
||||
cfg.Custom("msg_store", false, false, func() (interface{}, error) {
|
||||
var store module.BlobStore
|
||||
err := modconfig.ModuleFromNode("storage.blob", []string{"fs", "messages"},
|
||||
config.Node{}, nil, &store)
|
||||
return store, err
|
||||
}, func(m *config.Map, node config.Node) (interface{}, error) {
|
||||
if len(node.Args) != 1 {
|
||||
return nil, config.NodeErr(node, "expected 0 or 1 arguments")
|
||||
}
|
||||
return node.Args[0], nil
|
||||
}, &fsstoreLocation)
|
||||
var store module.BlobStore
|
||||
err := modconfig.ModuleFromNode("storage.blob", node.Args,
|
||||
node, m.Globals, &store)
|
||||
return store, err
|
||||
}, &blobStore)
|
||||
cfg.StringList("compression", false, false, []string{"off"}, &compression)
|
||||
cfg.DataSize("appendlimit", false, false, 32*1024*1024, &appendlimitVal)
|
||||
cfg.Bool("debug", true, false, &store.Log.Debug)
|
||||
|
@ -273,6 +150,14 @@ func (store *Storage) Init(cfg *config.Map) error {
|
|||
err := modconfig.GroupFromNode("imap_filters", node.Args, node, m.Globals, &filter)
|
||||
return filter, err
|
||||
}, &store.filters)
|
||||
cfg.Custom("auth_map", false, false, func() (interface{}, error) {
|
||||
return nil, nil
|
||||
}, modconfig.TableDirective, &store.authMap)
|
||||
cfg.String("auth_normalize", false, false, "precis_casefold_email", &authNormalize)
|
||||
cfg.Custom("delivery_map", false, false, func() (interface{}, error) {
|
||||
return nil, nil
|
||||
}, modconfig.TableDirective, &store.deliveryMap)
|
||||
cfg.String("delivery_normalize", false, false, "precis_casefold_email", &deliveryNormalize)
|
||||
|
||||
if _, err := cfg.Process(); err != nil {
|
||||
return err
|
||||
|
@ -285,6 +170,48 @@ func (store *Storage) Init(cfg *config.Map) error {
|
|||
return errors.New("imapsql: driver is required")
|
||||
}
|
||||
|
||||
deliveryNormFunc, ok := authz.NormalizeFuncs[deliveryNormalize]
|
||||
if !ok {
|
||||
return errors.New("imapsql: unknown normalization function: " + deliveryNormalize)
|
||||
}
|
||||
store.deliveryNormalize = func(ctx context.Context, s string) (string, error) {
|
||||
return deliveryNormFunc(s)
|
||||
}
|
||||
if store.deliveryMap != nil {
|
||||
store.deliveryNormalize = func(ctx context.Context, email string) (string, error) {
|
||||
email, err := deliveryNormFunc(email)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
mapped, ok, err := store.deliveryMap.Lookup(ctx, email)
|
||||
if err != nil || !ok {
|
||||
return "", userDoesNotExist(err)
|
||||
}
|
||||
return mapped, nil
|
||||
}
|
||||
}
|
||||
|
||||
authNormFunc, ok := authz.NormalizeFuncs[authNormalize]
|
||||
if !ok {
|
||||
return errors.New("imapsql: unknown normalization function: " + authNormalize)
|
||||
}
|
||||
store.authNormalize = func(ctx context.Context, s string) (string, error) {
|
||||
return authNormFunc(s)
|
||||
}
|
||||
if store.authMap != nil {
|
||||
store.authNormalize = func(ctx context.Context, username string) (string, error) {
|
||||
username, err := authNormFunc(username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
mapped, ok, err := store.authMap.Lookup(ctx, username)
|
||||
if err != nil || !ok {
|
||||
return "", userDoesNotExist(err)
|
||||
}
|
||||
return mapped, nil
|
||||
}
|
||||
}
|
||||
|
||||
opts.Log = &store.Log
|
||||
|
||||
if appendlimitVal == -1 {
|
||||
|
@ -302,11 +229,6 @@ func (store *Storage) Init(cfg *config.Map) error {
|
|||
|
||||
dsnStr := strings.Join(dsn, " ")
|
||||
|
||||
if err := os.MkdirAll(fsstoreLocation, os.ModeDir|os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
extStore := &imapsql.FSStore{Root: fsstoreLocation}
|
||||
|
||||
if len(compression) != 0 {
|
||||
switch compression[0] {
|
||||
case "zstd", "lz4":
|
||||
|
@ -329,7 +251,7 @@ func (store *Storage) Init(cfg *config.Map) error {
|
|||
}
|
||||
}
|
||||
|
||||
store.Back, err = imapsql.New(driver, dsnStr, extStore, opts)
|
||||
store.Back, err = imapsql.New(driver, dsnStr, ExtBlobStore{Base: blobStore}, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("imapsql: %s", err)
|
||||
}
|
||||
|
@ -387,6 +309,11 @@ func (store *Storage) EnableUpdatePipe(mode updatepipe.BackendMode) error {
|
|||
defer func() {
|
||||
store.updPushStop <- struct{}{}
|
||||
close(wrapped)
|
||||
|
||||
if err := recover(); err != nil {
|
||||
stack := debug.Stack()
|
||||
log.Printf("panic during imapsql update push: %v\n%s", err, stack)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
|
@ -440,34 +367,8 @@ func (store *Storage) EnableChildrenExt() bool {
|
|||
return store.Back.EnableChildrenExt()
|
||||
}
|
||||
|
||||
func prepareUsername(username string) (string, error) {
|
||||
mbox, domain, err := address.Split(username)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("imapsql: username prepare: %w", err)
|
||||
}
|
||||
|
||||
// PRECIS is not included in the regular address.ForLookup since it reduces
|
||||
// the range of valid addresses to a subset of actually valid values.
|
||||
// PRECIS is a matter of our own local policy, not a general rule for all
|
||||
// email addresses.
|
||||
|
||||
// Side note: For used profiles, there is no practical difference between
|
||||
// CompareKey and String.
|
||||
mbox, err = precis.UsernameCaseMapped.CompareKey(mbox)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("imapsql: username prepare: %w", err)
|
||||
}
|
||||
|
||||
domain, err = dns.ForLookup(domain)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("imapsql: username prepare: %w", err)
|
||||
}
|
||||
|
||||
return mbox + "@" + domain, nil
|
||||
}
|
||||
|
||||
func (store *Storage) GetOrCreateIMAPAcct(username string) (backend.User, error) {
|
||||
accountName, err := prepareUsername(username)
|
||||
accountName, err := store.authNormalize(context.TODO(), username)
|
||||
if err != nil {
|
||||
return nil, backend.ErrInvalidCredentials
|
||||
}
|
||||
|
@ -475,8 +376,8 @@ func (store *Storage) GetOrCreateIMAPAcct(username string) (backend.User, error)
|
|||
return store.Back.GetOrCreateUser(accountName)
|
||||
}
|
||||
|
||||
func (store *Storage) Lookup(key string) (string, bool, error) {
|
||||
accountName, err := prepareUsername(key)
|
||||
func (store *Storage) Lookup(ctx context.Context, key string) (string, bool, error) {
|
||||
accountName, err := store.authNormalize(ctx, key)
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
|
@ -521,7 +422,6 @@ func (store *Storage) SupportedThreadAlgorithms() []sortthread.ThreadAlgorithm {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("imapsql", "storage.imapsql", New)
|
||||
module.Register("storage.imapsql", New)
|
||||
module.Register("target.imapsql", New)
|
||||
}
|
||||
|
|
|
@ -29,29 +29,14 @@ func (store *Storage) ListIMAPAccts() ([]string, error) {
|
|||
return store.Back.ListUsers()
|
||||
}
|
||||
|
||||
func (store *Storage) CreateIMAPAcct(username string) error {
|
||||
accountName, err := prepareUsername(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Storage) CreateIMAPAcct(accountName string) error {
|
||||
return store.Back.CreateUser(accountName)
|
||||
}
|
||||
|
||||
func (store *Storage) DeleteIMAPAcct(username string) error {
|
||||
accountName, err := prepareUsername(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (store *Storage) DeleteIMAPAcct(accountName string) error {
|
||||
return store.Back.DeleteUser(accountName)
|
||||
}
|
||||
|
||||
func (store *Storage) GetIMAPAcct(username string) (backend.User, error) {
|
||||
accountName, err := prepareUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (store *Storage) GetIMAPAcct(accountName string) (backend.User, error) {
|
||||
return store.Back.GetUser(accountName)
|
||||
}
|
||||
|
|
99
internal/table/chain.go
Normal file
99
internal/table/chain.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
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 table
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
modconfig "github.com/foxcpp/maddy/framework/config/module"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
type Chain struct {
|
||||
modName string
|
||||
instName string
|
||||
|
||||
chain []module.Table
|
||||
optional []bool
|
||||
}
|
||||
|
||||
func NewChain(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
return &Chain{
|
||||
modName: modName,
|
||||
instName: instName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Chain) Init(cfg *config.Map) error {
|
||||
cfg.Callback("step", func(m *config.Map, node config.Node) error {
|
||||
var tbl module.Table
|
||||
err := modconfig.ModuleFromNode("table", node.Args, node, m.Globals, &tbl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.chain = append(s.chain, tbl)
|
||||
s.optional = append(s.optional, false)
|
||||
return nil
|
||||
})
|
||||
cfg.Callback("optional_step", func(m *config.Map, node config.Node) error {
|
||||
var tbl module.Table
|
||||
err := modconfig.ModuleFromNode("table", node.Args, node, m.Globals, &tbl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.chain = append(s.chain, tbl)
|
||||
s.optional = append(s.optional, true)
|
||||
return nil
|
||||
})
|
||||
|
||||
_, err := cfg.Process()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Chain) Name() string {
|
||||
return s.modName
|
||||
}
|
||||
|
||||
func (s *Chain) InstanceName() string {
|
||||
return s.instName
|
||||
}
|
||||
|
||||
func (s *Chain) Lookup(ctx context.Context, key string) (string, bool, error) {
|
||||
for i, step := range s.chain {
|
||||
val, ok, err := step.Lookup(ctx, key)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if !ok {
|
||||
if s.optional[i] {
|
||||
continue
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
key = val
|
||||
}
|
||||
return key, true, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.Register("table.chain", NewChain)
|
||||
}
|
64
internal/table/email_localpart.go
Normal file
64
internal/table/email_localpart.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
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 table
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/address"
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
||||
type EmailLocalpart struct {
|
||||
modName string
|
||||
instName string
|
||||
}
|
||||
|
||||
func NewEmailLocalpart(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
return &EmailLocalpart{
|
||||
modName: modName,
|
||||
instName: instName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *EmailLocalpart) Init(cfg *config.Map) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EmailLocalpart) Name() string {
|
||||
return s.modName
|
||||
}
|
||||
|
||||
func (s *EmailLocalpart) InstanceName() string {
|
||||
return s.modName
|
||||
}
|
||||
|
||||
func (s *EmailLocalpart) Lookup(ctx context.Context, key string) (string, bool, error) {
|
||||
mbox, _, err := address.Split(key)
|
||||
if err != nil {
|
||||
// Invalid email, no local part mapping.
|
||||
return "", false, nil
|
||||
}
|
||||
return mbox, true, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.Register("table.email_localpart", NewEmailLocalpart)
|
||||
}
|
|
@ -20,6 +20,7 @@ package table
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
@ -39,7 +40,7 @@ type File struct {
|
|||
instName string
|
||||
file string
|
||||
|
||||
m map[string]string
|
||||
m map[string][]string
|
||||
mLck sync.RWMutex
|
||||
mStamp time.Time
|
||||
|
||||
|
@ -52,7 +53,7 @@ type File struct {
|
|||
func NewFile(_, instName string, _, inlineArgs []string) (module.Module, error) {
|
||||
m := &File{
|
||||
instName: instName,
|
||||
m: make(map[string]string),
|
||||
m: make(map[string][]string),
|
||||
stopReloader: make(chan struct{}),
|
||||
forceReload: make(chan struct{}),
|
||||
log: log.Logger{Name: FileModName},
|
||||
|
@ -61,6 +62,7 @@ func NewFile(_, instName string, _, inlineArgs []string) (module.Module, error)
|
|||
switch len(inlineArgs) {
|
||||
case 1:
|
||||
m.file = inlineArgs[0]
|
||||
case 0:
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: cannot use multiple files with single %s, use %s multiple times to do so", FileModName, FileModName, FileModName)
|
||||
}
|
||||
|
@ -126,7 +128,7 @@ func (f *File) reloader() {
|
|||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
f.mLck.Lock()
|
||||
f.m = map[string]string{}
|
||||
f.m = map[string][]string{}
|
||||
f.mStamp = time.Now()
|
||||
f.mLck.Unlock()
|
||||
continue
|
||||
|
@ -144,7 +146,7 @@ func (f *File) reloader() {
|
|||
|
||||
f.log.Debugf("reloading")
|
||||
|
||||
newm := make(map[string]string, len(f.m)+5)
|
||||
newm := make(map[string][]string, len(f.m)+5)
|
||||
if err := readFile(f.file, newm); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
f.log.Printf("ignoring non-existent file: %s", f.file)
|
||||
|
@ -168,7 +170,7 @@ func (f *File) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func readFile(path string, out map[string]string) error {
|
||||
func readFile(path string, out map[string][]string) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -203,7 +205,7 @@ func readFile(path string, out map[string]string) error {
|
|||
}
|
||||
to := strings.TrimSpace(parts[1])
|
||||
|
||||
out[from] = to
|
||||
out[from] = append(out[from], to)
|
||||
}
|
||||
if err := scnr.Err(); err != nil {
|
||||
return err
|
||||
|
@ -212,7 +214,7 @@ func readFile(path string, out map[string]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *File) Lookup(val string) (string, bool, error) {
|
||||
func (f *File) Lookup(_ context.Context, val string) (string, bool, error) {
|
||||
// The existing map is never modified, instead it is replaced with a new
|
||||
// one if reload is performed.
|
||||
f.mLck.RLock()
|
||||
|
@ -220,10 +222,22 @@ func (f *File) Lookup(val string) (string, bool, error) {
|
|||
f.mLck.RUnlock()
|
||||
|
||||
newVal, ok := usedFile[val]
|
||||
return newVal, ok, nil
|
||||
|
||||
if len(newVal) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
return newVal[0], ok, nil
|
||||
}
|
||||
|
||||
func (f *File) LookupMulti(_ context.Context, val string) ([]string, error) {
|
||||
f.mLck.RLock()
|
||||
usedFile := f.m
|
||||
f.mLck.RUnlock()
|
||||
|
||||
return usedFile[val], nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("file", "table.file", NewFile)
|
||||
module.Register(FileModName, NewFile)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import (
|
|||
)
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
test := func(file string, expected map[string]string) {
|
||||
test := func(file string, expected map[string][]string) {
|
||||
t.Helper()
|
||||
|
||||
f, err := ioutil.TempFile("", "maddy-tests-")
|
||||
|
@ -43,7 +43,7 @@ func TestReadFile(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual := map[string]string{}
|
||||
actual := map[string][]string{}
|
||||
err = readFile(f.Name(), actual)
|
||||
if expected == nil {
|
||||
if err == nil {
|
||||
|
@ -61,24 +61,25 @@ func TestReadFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
test("a: b", map[string]string{"a": "b"})
|
||||
test("a@example.org: b@example.com", map[string]string{"a@example.org": "b@example.com"})
|
||||
test(`"a @ a"@example.org: b@example.com`, map[string]string{`"a @ a"@example.org`: "b@example.com"})
|
||||
test(`a@example.org: "b @ b"@example.com`, map[string]string{`a@example.org`: `"b @ b"@example.com`})
|
||||
test(`"a @ a": "b @ b"`, map[string]string{`"a @ a"`: `"b @ b"`})
|
||||
test("a: b, c", map[string]string{"a": "b, c"})
|
||||
test("a: b", map[string][]string{"a": {"b"}})
|
||||
test("a@example.org: b@example.com", map[string][]string{"a@example.org": {"b@example.com"}})
|
||||
test(`"a @ a"@example.org: b@example.com`, map[string][]string{`"a @ a"@example.org`: {"b@example.com"}})
|
||||
test(`a@example.org: "b @ b"@example.com`, map[string][]string{`a@example.org`: {`"b @ b"@example.com`}})
|
||||
test(`"a @ a": "b @ b"`, map[string][]string{`"a @ a"`: {`"b @ b"`}})
|
||||
test("a: b, c", map[string][]string{"a": {"b, c"}})
|
||||
test(": b", nil)
|
||||
test(":", nil)
|
||||
test("aaa", map[string]string{"aaa": ""})
|
||||
test("aaa", map[string][]string{"aaa": {""}})
|
||||
test(": b", nil)
|
||||
test(" testing@example.com : arbitrary-whitespace@example.org ",
|
||||
map[string]string{"testing@example.com": "arbitrary-whitespace@example.org"})
|
||||
map[string][]string{"testing@example.com": {"arbitrary-whitespace@example.org"}})
|
||||
test(`# skip comments
|
||||
a: b`, map[string]string{"a": "b"})
|
||||
a: b`, map[string][]string{"a": {"b"}})
|
||||
test(`# and empty lines
|
||||
|
||||
a: b`, map[string]string{"a": "b"})
|
||||
test("# with whitespace too\n \na: b", map[string]string{"a": "b"})
|
||||
a: b`, map[string][]string{"a": {"b"}})
|
||||
test("# with whitespace too\n \na: b", map[string][]string{"a": {"b"}})
|
||||
test("a: b\na: c", map[string][]string{"a": {"b", "c"}})
|
||||
}
|
||||
|
||||
func TestFileReload(t *testing.T) {
|
||||
|
@ -119,7 +120,7 @@ func TestFileReload(t *testing.T) {
|
|||
for i := 0; i < 10; i++ {
|
||||
time.Sleep(reloadInterval + 50*time.Millisecond)
|
||||
m.mLck.RLock()
|
||||
if m.m["dog"] != "" {
|
||||
if m.m["dog"] != nil {
|
||||
m.mLck.RUnlock()
|
||||
break
|
||||
}
|
||||
|
@ -128,7 +129,7 @@ func TestFileReload(t *testing.T) {
|
|||
|
||||
m.mLck.RLock()
|
||||
defer m.mLck.RUnlock()
|
||||
if m.m["dog"] == "" {
|
||||
if m.m["dog"] == nil {
|
||||
t.Fatal("New m were not loaded")
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +175,7 @@ func TestFileReload_Broken(t *testing.T) {
|
|||
|
||||
m.mLck.RLock()
|
||||
defer m.mLck.RUnlock()
|
||||
if m.m["cat"] == "" {
|
||||
if m.m["cat"] == nil {
|
||||
t.Fatal("New m were loaded or map changed", m.m)
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +216,7 @@ func TestFileReload_Removed(t *testing.T) {
|
|||
|
||||
m.mLck.RLock()
|
||||
defer m.mLck.RUnlock()
|
||||
if m.m["cat"] != "" {
|
||||
if m.m["cat"] != nil {
|
||||
t.Fatal("Old m are still loaded")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package table
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
@ -47,11 +49,10 @@ func (s *Identity) InstanceName() string {
|
|||
return s.modName
|
||||
}
|
||||
|
||||
func (s *Identity) Lookup(key string) (string, bool, error) {
|
||||
func (s *Identity) Lookup(_ context.Context, key string) (string, bool, error) {
|
||||
return key, true, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("identity", "table.identity", NewIdentity)
|
||||
module.Register("table.identity", NewIdentity)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package table
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -95,7 +96,7 @@ func (r *Regexp) InstanceName() string {
|
|||
return r.modName
|
||||
}
|
||||
|
||||
func (r *Regexp) Lookup(key string) (string, bool, error) {
|
||||
func (r *Regexp) Lookup(_ context.Context, key string) (string, bool, error) {
|
||||
matches := r.re.FindStringSubmatchIndex(key)
|
||||
if matches == nil {
|
||||
return "", false, nil
|
||||
|
@ -109,6 +110,5 @@ func (r *Regexp) Lookup(key string) (string, bool, error) {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("regexp", "table.regexp", NewRegexp)
|
||||
module.Register("table.regexp", NewRegexp)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package table
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
@ -137,15 +138,15 @@ func (s *SQL) Close() error {
|
|||
return s.db.Close()
|
||||
}
|
||||
|
||||
func (s *SQL) Lookup(val string) (string, bool, error) {
|
||||
func (s *SQL) Lookup(ctx context.Context, val string) (string, bool, error) {
|
||||
var (
|
||||
repl string
|
||||
row *sql.Row
|
||||
)
|
||||
if s.namedArgs {
|
||||
row = s.lookup.QueryRow(sql.Named("key", val))
|
||||
row = s.lookup.QueryRowContext(ctx, sql.Named("key", val))
|
||||
} else {
|
||||
row = s.lookup.QueryRow(val)
|
||||
row = s.lookup.QueryRowContext(ctx, val)
|
||||
}
|
||||
if err := row.Scan(&repl); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -156,6 +157,33 @@ func (s *SQL) Lookup(val string) (string, bool, error) {
|
|||
return repl, true, nil
|
||||
}
|
||||
|
||||
func (s *SQL) LookupMulti(ctx context.Context, val string) ([]string, error) {
|
||||
var (
|
||||
repl []string
|
||||
rows *sql.Rows
|
||||
err error
|
||||
)
|
||||
if s.namedArgs {
|
||||
rows, err = s.lookup.QueryContext(ctx, sql.Named("key", val))
|
||||
} else {
|
||||
rows, err = s.lookup.QueryContext(ctx, val)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s; lookup %s: %w", s.modName, val, err)
|
||||
}
|
||||
for rows.Next() {
|
||||
var res string
|
||||
if err := rows.Scan(&res); err != nil {
|
||||
return nil, fmt.Errorf("%s; lookup %s: %w", s.modName, val, err)
|
||||
}
|
||||
repl = append(repl, res)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("%s; lookup %s: %w", s.modName, val, err)
|
||||
}
|
||||
return repl, nil
|
||||
}
|
||||
|
||||
func (s *SQL) Keys() ([]string, error) {
|
||||
if s.list == nil {
|
||||
return nil, fmt.Errorf("%s: table is not mutable (no 'list' query)", s.modName)
|
||||
|
@ -219,6 +247,5 @@ func (s *SQL) SetKey(k, v string) error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("sql_query", "table.sql_query", NewSQL)
|
||||
module.Register("table.sql_query", NewSQL)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package table
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
|
@ -48,8 +50,9 @@ func TestSQL(t *testing.T) {
|
|||
{
|
||||
Name: "init",
|
||||
Args: []string{
|
||||
"CREATE TABLE testTbl (key TEXT PRIMARY KEY , value TEXT)",
|
||||
"CREATE TABLE testTbl (key TEXT, value TEXT)",
|
||||
"INSERT INTO testTbl VALUES ('user1', 'user1a')",
|
||||
"INSERT INTO testTbl VALUES ('user1', 'user1b')",
|
||||
"INSERT INTO testTbl VALUES ('user3', NULL)",
|
||||
},
|
||||
},
|
||||
|
@ -66,7 +69,7 @@ func TestSQL(t *testing.T) {
|
|||
check := func(key, res string, ok, fail bool) {
|
||||
t.Helper()
|
||||
|
||||
actualRes, actualOk, err := tbl.Lookup(key)
|
||||
actualRes, actualOk, err := tbl.Lookup(context.Background(), key)
|
||||
if actualRes != res {
|
||||
t.Errorf("Result mismatch: want %s, got %s", res, actualRes)
|
||||
}
|
||||
|
@ -81,4 +84,12 @@ func TestSQL(t *testing.T) {
|
|||
check("user1", "user1a", true, false)
|
||||
check("user2", "", false, false)
|
||||
check("user3", "", false, true)
|
||||
|
||||
vals, err := tbl.LookupMulti(context.Background(), "user1")
|
||||
if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
if !reflect.DeepEqual(vals, []string{"user1a", "user1b"}) {
|
||||
t.Error("Wrong result of LookupMulti:", vals)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package table
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
|
@ -147,8 +148,12 @@ func (s *SQLTable) Close() error {
|
|||
return s.wrapped.Close()
|
||||
}
|
||||
|
||||
func (s *SQLTable) Lookup(val string) (string, bool, error) {
|
||||
return s.wrapped.Lookup(val)
|
||||
func (s *SQLTable) Lookup(ctx context.Context, val string) (string, bool, error) {
|
||||
return s.wrapped.Lookup(ctx, val)
|
||||
}
|
||||
|
||||
func (s *SQLTable) LookupMulti(ctx context.Context, val string) ([]string, error) {
|
||||
return s.wrapped.LookupMulti(ctx, val)
|
||||
}
|
||||
|
||||
func (s *SQLTable) Keys() ([]string, error) {
|
||||
|
@ -164,6 +169,5 @@ func (s *SQLTable) SetKey(k, v string) error {
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("sql_table", "table.sql_table", NewSQLTable)
|
||||
module.Register("table.sql_table", NewSQLTable)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||
package table
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/foxcpp/maddy/framework/config"
|
||||
"github.com/foxcpp/maddy/framework/module"
|
||||
)
|
||||
|
@ -27,23 +29,23 @@ type Static struct {
|
|||
modName string
|
||||
instName string
|
||||
|
||||
m map[string]string
|
||||
m map[string][]string
|
||||
}
|
||||
|
||||
func NewStatic(modName, instName string, _, _ []string) (module.Module, error) {
|
||||
return &Static{
|
||||
modName: modName,
|
||||
instName: instName,
|
||||
m: map[string]string{},
|
||||
m: map[string][]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Static) Init(cfg *config.Map) error {
|
||||
cfg.Callback("entry", func(m *config.Map, node config.Node) error {
|
||||
if len(node.Args) != 2 {
|
||||
return config.NodeErr(node, "expected exactly two arguments")
|
||||
if len(node.Args) < 2 {
|
||||
return config.NodeErr(node, "expected at least one value")
|
||||
}
|
||||
s.m[node.Args[0]] = node.Args[1]
|
||||
s.m[node.Args[0]] = node.Args[1:]
|
||||
return nil
|
||||
})
|
||||
_, err := cfg.Process()
|
||||
|
@ -58,12 +60,14 @@ func (s *Static) InstanceName() string {
|
|||
return s.modName
|
||||
}
|
||||
|
||||
func (s *Static) Lookup(key string) (string, bool, error) {
|
||||
val, ok := s.m[key]
|
||||
return val, ok, nil
|
||||
func (s *Static) Lookup(ctx context.Context, key string) (string, bool, error) {
|
||||
val := s.m[key]
|
||||
if len(val) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
return val[0], true, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("static", "table.static", NewStatic)
|
||||
module.Register("table.static", NewStatic)
|
||||
}
|
||||
|
|
|
@ -995,6 +995,5 @@ func (q *Queue) emitDSN(meta *QueueMetadata, header textproto.Header, failedRcpt
|
|||
}
|
||||
|
||||
func init() {
|
||||
module.RegisterDeprecated("queue", "target.queue", NewQueue)
|
||||
module.Register("target.queue", NewQueue)
|
||||
}
|
||||
|
|
|
@ -285,6 +285,15 @@ func (rd *remoteDelivery) newConn(ctx context.Context, domain string) (*mxConn,
|
|||
conn.Log = rd.Log
|
||||
conn.Hostname = rd.rt.hostname
|
||||
conn.AddrInSMTPMsg = true
|
||||
if rd.rt.connectTimeout != 0 {
|
||||
conn.ConnectTimeout = rd.rt.connectTimeout
|
||||
}
|
||||
if rd.rt.commandTimeout != 0 {
|
||||
conn.CommandTimeout = rd.rt.commandTimeout
|
||||
}
|
||||
if rd.rt.submissionTimeout != 0 {
|
||||
conn.SubmissionTimeout = rd.rt.submissionTimeout
|
||||
}
|
||||
|
||||
for _, p := range rd.policies {
|
||||
p.PrepareDomain(ctx, domain)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue