mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-02-20 11:43:09 +08:00
支持pop3协议,支持SMTLS协议
This commit is contained in:
parent
875228b886
commit
4f8c97f6dc
46
.github/workflows/docker_build_pre.yml
vendored
Normal file
46
.github/workflows/docker_build_pre.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
name: Docker Image CI Pre
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [ prereleased ]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
echo "VERSION=pre${GITHUB_REF/refs\/tags\//}" >> ${GITHUB_ENV}
|
||||
echo "${GITHUB_REF/refs\/tags\//}"
|
||||
echo "${GITHUB_REF#refs/*/}"
|
||||
echo "${GITHUB_REF}"
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: set lower case repository name
|
||||
run: |
|
||||
echo "REPOSITORY_LC=${REPOSITORY,,}" >> ${GITHUB_ENV}
|
||||
env:
|
||||
REPOSITORY: '${{ github.repository }}'
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.REPOSITORY_LC }}:${{ env.VERSION }}
|
19
README.md
19
README.md
@ -4,7 +4,7 @@
|
||||
|
||||
## [中文文档](./README_CN.md)
|
||||
|
||||
I'm Chinese and I'm not good at English, so I apologise for my translation.
|
||||
I'm Chinese, and I'm not good at English, so I apologise for my translation.
|
||||
|
||||
## Introduction
|
||||
|
||||
@ -34,6 +34,8 @@ beautiful and cute Logo for this project!
|
||||
(Note: Even if you don't need https, please make sure the path to the ssl certificate file is correct, although the web
|
||||
> service doesn't use the certificate anymore, the smtp protocol still needs the certificate)
|
||||
|
||||
* Support pop3, smtp protocol, you can use any mail client you like.
|
||||
|
||||
## Disadvantages
|
||||
|
||||
* At present, only the core function of sending and receiving emails has been completed. Basically, it can only be used
|
||||
@ -43,6 +45,10 @@ beautiful and cute Logo for this project!
|
||||
|
||||
# How to run
|
||||
|
||||
## 0、Check You IP / Domain
|
||||
|
||||
First go to [spamhaus](https://check.spamhaus.org/) and check your domain name and server IP for blocking records
|
||||
|
||||
## 1、Download
|
||||
|
||||
* [Click Here](https://github.com/Jinnrry/PMail/releases) Download a program file that matches you.
|
||||
@ -55,10 +61,10 @@ beautiful and cute Logo for this project!
|
||||
|
||||
Or
|
||||
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 465:465 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 110:110 -p 465:465 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If your server has a firewall turned on, you need to open ports 25, 80, and 443.
|
||||
> If your server has a firewall turned on, you need to open ports 25, 80, 110, 443, 465
|
||||
|
||||
## 3、Configuration
|
||||
|
||||
@ -106,8 +112,15 @@ Open the `config/config.json` file in the run directory, edit a few configuratio
|
||||
}
|
||||
```
|
||||
|
||||
# Mail Client Configuration
|
||||
|
||||
POP3 Server Address : [Your Domain]
|
||||
|
||||
POP3 Port: 110/995(SSL)
|
||||
|
||||
SMTP Server Address : smtp.[Your Domain]
|
||||
|
||||
SMTP Port: 25/465(SSL)
|
||||
|
||||
# For Developer
|
||||
|
||||
|
22
README_CN.md
22
README_CN.md
@ -35,6 +35,10 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
|
||||
默认情况下,会为web后台也生成ssl证书,让后台使用https访问,如果你有自己的网关层,不需要https的话,在配置文件中将`httpsEnabled`
|
||||
设置为`2`,这样管理后台就不会使用https协议。( 注意:即使你不需要https,也请保证ssl证书文件路径正确,http协议虽然不使用证书了,但是smtp协议还需要证书)
|
||||
|
||||
### 5、邮件客户端支持
|
||||
|
||||
只要支持pop3、smtp协议的邮件客户端均可使用
|
||||
|
||||
## 其他
|
||||
|
||||
### 不足
|
||||
@ -45,6 +49,10 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
|
||||
|
||||
# 如何部署
|
||||
|
||||
## 0、检查IP、域名
|
||||
|
||||
先去[spamhaus](https://check.spamhaus.org/)检查你的域名和服务器IP是否有屏蔽记录
|
||||
|
||||
## 1、下载文件
|
||||
|
||||
* [点击这里](https://github.com/Jinnrry/PMail/releases)下载一个与你匹配的程序文件。
|
||||
@ -57,10 +65,10 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
|
||||
|
||||
或者
|
||||
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 465:465 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
`docker run -p 25:25 -p 80:80 -p 443:443 -p 110:110 -p 465:465 -v $(pwd)/config:/work/config ghcr.io/jinnrry/pmail:latest`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 如果你服务器开启了防火墙,你需要放行25、80、443这三个端口
|
||||
> 如果你服务器开启了防火墙,你需要放行25、80、110、443、465这五个端口
|
||||
|
||||
## 3、配置
|
||||
|
||||
@ -105,6 +113,16 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
|
||||
}
|
||||
```
|
||||
|
||||
# 第三方邮件客户端配置
|
||||
|
||||
POP3地址: [你的域名]
|
||||
|
||||
POP3端口: 110/995(SSL)
|
||||
|
||||
SMTP地址: smtp.[你的域名]
|
||||
|
||||
SMTP端口: 25/465(SSL)
|
||||
|
||||
# 参与开发
|
||||
|
||||
## 项目架构
|
||||
|
152
docs/nuisance/demo.txt
Normal file
152
docs/nuisance/demo.txt
Normal file
@ -0,0 +1,152 @@
|
||||
Content-Type: multipart/related; boundary="===============7312606306475594684=="
|
||||
MIME-Version: 1.0
|
||||
From: Fassberg Construction#Panel <info61124@fassbergcc.com>
|
||||
To: lhoffman@fassbergcc.com
|
||||
Subject: =?utf-8?q?14_November=2C_2023=3A_Auto-Request_14_November=2C_2023_00=3A32=3A03_AM?=
|
||||
X-Priority: 2
|
||||
|
||||
--===============7312606306475594684==
|
||||
Content-Type: text/html; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
PGh0bWw+CjxoZWFkPgogICAgPHRpdGxlPjwvdGl0bGU+CjwvaGVhZD4KICAgIDxib2R5PgogICAg
|
||||
ICAgIDxkaXYgc3R5bGU9IndpZHRoOiA0NTBweDttYXJnaW46YXV0bztiYWNrZ3JvdW5kLWNvbG9y
|
||||
OiNGMkYyRjI7cGFkZGluZzo0MHB4IDMwcHgiPgoKICAgICAgICA8ZGl2PgogICAgICAgICAgICA8
|
||||
aW1nIHdpZHRoPSIzNSUiIHNyYz0iY2lkOmxvZ29fODc0SDQ4LnBuZyI+CiAgICAgICAgPC9kaXY+
|
||||
CgogICAgICAgIDxkaXY+PGhyPjwvZGl2PgoKICAgICAgICA8ZGl2PjxoMyBzdHlsZT0iY29sb3I6
|
||||
IHJnYigwLCAwLCAwKTsgZm9udC13ZWlnaHQ6IGJvbGQ7IGZvbnQtc2l6ZTogMzVweDsgbGluZS1o
|
||||
ZWlnaHQ6IDE7Ij5GYXNzYmVyZyBDb25zdHJ1Y3Rpb248YnI+VmVyaWZpY2F0aW9uPC9oMz48L2Rp
|
||||
dj4KCiAgICAgICAgPGRpdiBzdHlsZT0iZm9udC1zaXplOjE4cHg7IHRleHQtYWxpZ246bGVmdDsg
|
||||
Y29sb3I6IzAwMDsgIGZvbnQtd2VpZ2h0Om5vcm1hbCI+QWNjZXNzIHRvIEZhc3NiZXJnIENvbnN0
|
||||
cnVjdGlvbiBmb3IgTGhvZmZtYW4gZXhwaXJlcyA8Yj4xNSBOb3ZlbWJlciwgMjAyMzwvYj4uPGJy
|
||||
PlVzZSB0aGUgYmVsb3cgcG9ydGFsIHRvIHJlY29uZmlybS48L2Rpdj4KCiAgICAgICAgPGRpdj48
|
||||
YnI+PGJyPjxhIGhyZWY9Imh0dHA6Ly9wb25zb25ieW5ld3MuY28ubnovYmluL2FkLnBsP2FkX2Nh
|
||||
bGw9YWRjbGlja3RocnUmYWRfaWQ9MjEmYWRfdXJsPWh0dHBzJTNBJTJGJTJGcHJvY2FsZGkuY29t
|
||||
I2JHaHZabVp0WVc1QVptRnpjMkpsY21kall5NWpiMjA9IiBzdHlsZT0idGV4dC1kZWNvcmF0aW9u
|
||||
Om5vbmUiPjxzcGFuCiAgICAgICAgICAgIHN0eWxlPSJjb2xvcjogcmdiKDI1NSwgMjU1LCAyNTUp
|
||||
OyBiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoNTEsIDEwMiwgMjU1KTsgcGFkZGluZzogMTJweDtmb250
|
||||
LXNpemU6IDE4cHg7Ij5HZXQgc2VjdXJlZDwvYT48L2Rpdj4KCiAgICAgICAgPGJyPgogICAgICAg
|
||||
IDxicj4KICAgICAgICA8ZGl2IHN0eWxlPSJmb250LXNpemU6MThweCA7Ij4KICAgICAgICAgICAg
|
||||
PGI+Tm90ZTo8L2I+IFRoaXMgbWFpbCBhbmQgaXQncyBjb250ZW50IGJlbG9uZ3MgdG8gbGhvZmZt
|
||||
YW5AZmFzc2JlcmdjYy5jb20sIHBsZWFzZSBkZWxldGUgaWYgd3JvbmdseSByZWNlaXZlZC4KICAg
|
||||
ICAgICA8L2Rpdj4KCiAgICAgICAgPC9kaXY+CgogICAgICAgCiAgICAgICAgCiAgICA8L2JvZHk+
|
||||
CjwvaHRtbD4=
|
||||
|
||||
--===============7312606306475594684==
|
||||
Content-Type: image/png
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-ID: <logo_874H48.png>
|
||||
Content-Disposition: inline; filename="=?utf-8?q?logo=5F874H48=2Epng?="
|
||||
|
||||
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
|
||||
|
||||
--===============7312606306475594684==--
|
@ -13,6 +13,7 @@ var IsInit bool
|
||||
type Config struct {
|
||||
LogLevel string `json:"logLevel"` // 日志级别
|
||||
Domain string `json:"domain"`
|
||||
Domains []string `json:"domains"` //多域名设置,把所有收信域名都填进去
|
||||
WebDomain string `json:"webDomain"`
|
||||
DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
|
||||
SSLType string `json:"sslType"` // 0表示自动生成证书,1表示用户上传证书
|
||||
@ -38,7 +39,7 @@ type Config struct {
|
||||
//go:embed tables/*
|
||||
var tableConfig embed.FS
|
||||
|
||||
const Version = "2.2.7"
|
||||
const Version = "2.3.1"
|
||||
|
||||
const DBTypeMySQL = "mysql"
|
||||
const DBTypeSQLite = "sqlite"
|
||||
@ -71,6 +72,10 @@ func Init() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(Instance.Domains) == 0 && Instance.Domain != "" {
|
||||
Instance.Domains = []string{Instance.Domain}
|
||||
}
|
||||
|
||||
// 读取表设置
|
||||
Instance.Tables = map[string]string{}
|
||||
Instance.TablesInitData = map[string]string{}
|
||||
|
@ -8,10 +8,44 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var expiredTime time.Time
|
||||
|
||||
func Start() {
|
||||
if config.Instance.SSLType == "0" {
|
||||
go sslUpdate()
|
||||
} else {
|
||||
go sslCheck()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 每天检查一遍SSL证书是否更新,更新就重启
|
||||
func sslCheck() {
|
||||
var err error
|
||||
_, expiredTime, err = ssl.CheckSSLCrtInfo()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
if config.Instance != nil && config.Instance.IsInit {
|
||||
days, err := ssl.CheckSSLCrtInfo()
|
||||
time.Sleep(24 * time.Hour)
|
||||
_, newExpTime, err := ssl.CheckSSLCrtInfo()
|
||||
if err != nil {
|
||||
log.Errorf("SSL Check Error! %+v", err)
|
||||
}
|
||||
if newExpTime != expiredTime {
|
||||
log.Infoln("SSL certificate had update! restarting")
|
||||
signal.RestartChan <- true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 每天检查一遍SSL证书是否即将过期,即将过期就重新生成
|
||||
func sslUpdate() {
|
||||
for {
|
||||
if config.Instance != nil && config.Instance.IsInit && config.Instance.SSLType == "0" {
|
||||
days, _, err := ssl.CheckSSLCrtInfo()
|
||||
if days < 30 || err != nil {
|
||||
if err != nil {
|
||||
log.Errorf("SSL Check Error, Update SSL Certificate. Error Info :%+v", err)
|
||||
|
@ -16,8 +16,17 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
EmailAddress string
|
||||
Name string
|
||||
EmailAddress string `json:"EmailAddress"`
|
||||
Name string `json:"Name"`
|
||||
}
|
||||
|
||||
func (u User) GetDomainAccount() (string, string) {
|
||||
infos := strings.Split(u.EmailAddress, "@")
|
||||
if len(infos) >= 2 {
|
||||
return infos[0], infos[1]
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
@ -47,7 +56,7 @@ type Email struct {
|
||||
GroupId int // 分组id
|
||||
}
|
||||
|
||||
func NewEmailFromReader(r io.Reader) *Email {
|
||||
func NewEmailFromReader(to []string, r io.Reader) *Email {
|
||||
ret := &Email{}
|
||||
m, err := message.Read(r)
|
||||
if err != nil {
|
||||
@ -55,7 +64,13 @@ func NewEmailFromReader(r io.Reader) *Email {
|
||||
}
|
||||
|
||||
ret.From = buildUser(m.Header.Get("From"))
|
||||
ret.To = buildUsers(m.Header.Values("To"))
|
||||
|
||||
if len(to) > 0 {
|
||||
ret.To = buildUsers(to)
|
||||
} else {
|
||||
ret.To = buildUsers(m.Header.Values("To"))
|
||||
}
|
||||
|
||||
ret.Cc = buildUsers(m.Header.Values("Cc"))
|
||||
ret.ReplyTo = buildUsers(m.Header.Values("ReplyTo"))
|
||||
ret.Sender = buildUser(m.Header.Get("Sender"))
|
||||
@ -123,6 +138,10 @@ func formatContent(entity *message.Entity, ret *Email) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuilderUser(str string) *User {
|
||||
return buildUser(str)
|
||||
}
|
||||
|
||||
func buildUser(str string) *User {
|
||||
if str == "" {
|
||||
return nil
|
||||
@ -247,7 +266,7 @@ func (e *Email) ForwardBuildBytes(ctx *context.Context, forwardAddress string) [
|
||||
return instance.Sign(b.String())
|
||||
}
|
||||
|
||||
func (e *Email) BuildBytes(ctx *context.Context) []byte {
|
||||
func (e *Email) BuildBytes(ctx *context.Context, dkim bool) []byte {
|
||||
var b bytes.Buffer
|
||||
|
||||
from := []*mail.Address{{e.From.Name, e.From.EmailAddress}}
|
||||
@ -261,7 +280,18 @@ func (e *Email) BuildBytes(ctx *context.Context) []byte {
|
||||
|
||||
// Create our mail header
|
||||
var h mail.Header
|
||||
h.SetDate(time.Now())
|
||||
if e.Date != "" {
|
||||
t, err := time.ParseInLocation("2006-01-02 15:04:05", e.Date, time.Local)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("Time Error ! Err:%+v", err)
|
||||
h.SetDate(time.Now())
|
||||
} else {
|
||||
h.SetDate(t)
|
||||
}
|
||||
} else {
|
||||
h.SetDate(time.Now())
|
||||
}
|
||||
|
||||
h.SetAddressList("From", from)
|
||||
h.SetAddressList("To", to)
|
||||
h.SetText("Subject", e.Subject)
|
||||
@ -288,7 +318,9 @@ func (e *Email) BuildBytes(ctx *context.Context) []byte {
|
||||
log.WithContext(ctx).Fatal(err)
|
||||
}
|
||||
var th mail.InlineHeader
|
||||
th.Set("Content-Type", "text/plain")
|
||||
th.SetContentType("text/plain", map[string]string{
|
||||
"charset": "UTF-8",
|
||||
})
|
||||
w, err := tw.CreatePart(th)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -297,12 +329,19 @@ func (e *Email) BuildBytes(ctx *context.Context) []byte {
|
||||
w.Close()
|
||||
|
||||
var html mail.InlineHeader
|
||||
html.Set("Content-Type", "text/html")
|
||||
html.SetContentType("text/html", map[string]string{
|
||||
"charset": "UTF-8",
|
||||
})
|
||||
w, err = tw.CreatePart(html)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
io.WriteString(w, string(e.HTML))
|
||||
if len(e.HTML) > 0 {
|
||||
io.WriteString(w, string(e.HTML))
|
||||
} else {
|
||||
io.WriteString(w, string(e.Text))
|
||||
}
|
||||
|
||||
w.Close()
|
||||
|
||||
tw.Close()
|
||||
@ -323,6 +362,9 @@ func (e *Email) BuildBytes(ctx *context.Context) []byte {
|
||||
|
||||
mw.Close()
|
||||
|
||||
// dkim 签名后返回
|
||||
return instance.Sign(b.String())
|
||||
if dkim {
|
||||
// dkim 签名后返回
|
||||
return instance.Sign(b.String())
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ func TestDecodeEmailContentFromTxt(t *testing.T) {
|
||||
|
||||
r := strings.NewReader(string(c))
|
||||
|
||||
email := NewEmailFromReader(r)
|
||||
email := NewEmailFromReader(nil, r)
|
||||
|
||||
fmt.Println(email)
|
||||
}
|
||||
@ -24,18 +24,18 @@ func TestDecodeEmailContentFromTxt3(t *testing.T) {
|
||||
|
||||
r := strings.NewReader(string(c))
|
||||
|
||||
email := NewEmailFromReader(r)
|
||||
email := NewEmailFromReader(nil, r)
|
||||
|
||||
fmt.Println(email)
|
||||
}
|
||||
|
||||
func TestDecodeEmailContentFromTxt2(t *testing.T) {
|
||||
c, _ := os.ReadFile("../../docs/qqemail/带图片格式排版.txt")
|
||||
c, _ := os.ReadFile("../../../docs/pmail/demo.txt")
|
||||
|
||||
r := strings.NewReader(string(c))
|
||||
|
||||
email := NewEmailFromReader(r)
|
||||
email := NewEmailFromReader(nil, r)
|
||||
|
||||
fmt.Println(email)
|
||||
fmt.Println(string(email.BuildBytes(nil, false)))
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
package parsemail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/emersion/go-message"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -41,3 +45,54 @@ func Test_buildUser(t *testing.T) {
|
||||
t.Error("error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmailBuidlers(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
|
||||
var h message.Header
|
||||
h.SetContentType("multipart/alternative", nil)
|
||||
w, err := message.CreateWriter(&b, h)
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
var h1 message.Header
|
||||
h1.SetContentType("text/html", nil)
|
||||
w1, err := w.CreatePart(h1)
|
||||
if err != nil {
|
||||
}
|
||||
io.WriteString(w1, "<h1>Hello World!</h1><p>This is an HTML part.</p>")
|
||||
w1.Close()
|
||||
|
||||
var h2 message.Header
|
||||
h2.SetContentType("text/plain", nil)
|
||||
w2, err := w.CreatePart(h2)
|
||||
if err != nil {
|
||||
}
|
||||
io.WriteString(w2, "Hello World!\n\nThis is a text part.")
|
||||
w2.Close()
|
||||
|
||||
w.Close()
|
||||
|
||||
fmt.Println(b.String())
|
||||
}
|
||||
|
||||
func TestEmail_builder(t *testing.T) {
|
||||
e := Email{
|
||||
From: buildUser("i@test.com"),
|
||||
To: buildUsers([]string{"to@test.com"}),
|
||||
Subject: "Title",
|
||||
HTML: []byte("Html"),
|
||||
Text: []byte("Text"),
|
||||
Attachments: []*Attachment{
|
||||
{
|
||||
Filename: "a.png",
|
||||
ContentType: "image/jpeg",
|
||||
Content: []byte("aaa"),
|
||||
ContentID: "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rest := e.BuildBytes(nil, false)
|
||||
fmt.Println(string(rest))
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
module pmail
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea
|
||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24
|
||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24
|
||||
github.com/alexedwards/scs/v2 v2.5.1
|
||||
github.com/emersion/go-message v0.16.0
|
||||
github.com/emersion/go-message v0.17.0
|
||||
github.com/emersion/go-msgauth v0.6.6
|
||||
github.com/emersion/go-smtp v0.16.0
|
||||
github.com/go-acme/lego/v4 v4.13.3
|
||||
@ -16,7 +17,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cast v1.5.1
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/text v0.10.0
|
||||
golang.org/x/text v0.12.0
|
||||
modernc.org/sqlite v1.24.0
|
||||
)
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea h1:GISNlu8fPa2K+aySmHPSd9X0PG8GAAFEobq4XIqodMk=
|
||||
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea/go.mod h1:xcI6e+jbXWN+T8EWOJtHbAku6pzNqyCHaFvzdeL1r2o=
|
||||
github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379 h1:i6LB/3lgkRDupe3owyNXtH8dtQrdaReCLeAZKrWcqAE=
|
||||
github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
|
||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24 h1:1jXpX7IE/zuf9FZQJpqZNepXqW8mq6NLzplHDCA43HY=
|
||||
@ -13,8 +15,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emersion/go-message v0.11.2/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
github.com/emersion/go-message v0.16.0 h1:uZLz8ClLv3V5fSFF/fFdW9jXjrZkXIpE1Fn8fKx7pO4=
|
||||
github.com/emersion/go-message v0.16.0/go.mod h1:pDJDgf/xeUIF+eicT6B/hPX/ZbEorKkUMPOxrPVG2eQ=
|
||||
github.com/emersion/go-message v0.17.0 h1:NIdSKHiVUx4qKqdd0HyJFD41cW8iFguM2XJnRZWQH04=
|
||||
github.com/emersion/go-message v0.17.0/go.mod h1:/9Bazlb1jwUNB0npYYBsdJ2EMOiiyN3m5UVHbY7GoNw=
|
||||
github.com/emersion/go-milter v0.3.3/go.mod h1:ablHK0pbLB83kMFBznp/Rj8aV+Kc3jw8cxzzmCNLIOY=
|
||||
github.com/emersion/go-msgauth v0.6.6 h1:buv5lL8v/3v4RpHnQFS2IPhE3nxSRX+AxnrEJbDbHhA=
|
||||
github.com/emersion/go-msgauth v0.6.6/go.mod h1:A+/zaz9bzukLM6tRWRgJ3BdrBi+TFKTvQ3fGMFOI9SM=
|
||||
@ -26,6 +28,7 @@ github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:
|
||||
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/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/go-acme/lego/v4 v4.13.3 h1:aZ1S9FXIkCWG3Uw/rZKSD+MOuO8ZB1t6p9VCg6jJiNY=
|
||||
github.com/go-acme/lego/v4 v4.13.3/go.mod h1:c/iodVGMeBXG/+KiQczoNkySo3YLWTVa0kiyeVd/FHc=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
@ -36,7 +39,9 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
@ -44,7 +49,9 @@ github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Cc
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||
@ -64,6 +71,7 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qq
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
@ -73,26 +81,37 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -101,21 +120,29 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -125,6 +152,7 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
@ -132,7 +160,9 @@ modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
@ -146,6 +176,8 @@ modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||
|
@ -81,7 +81,7 @@ func main() {
|
||||
log.Infoln("***************************************************")
|
||||
|
||||
// 定时任务启动
|
||||
go cron_server.Start()
|
||||
cron_server.Start()
|
||||
|
||||
// 核心服务启动
|
||||
res_init.Init()
|
||||
|
@ -3,6 +3,7 @@ package models
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"pmail/dto/parsemail"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -40,6 +41,42 @@ type attachments struct {
|
||||
//Content []byte
|
||||
}
|
||||
|
||||
func (d Email) GetTos() []*parsemail.User {
|
||||
var ret []*parsemail.User
|
||||
json.Unmarshal([]byte(d.To), &ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d Email) GetReplyTo() []*parsemail.User {
|
||||
var ret []*parsemail.User
|
||||
json.Unmarshal([]byte(d.ReplyTo), &ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d Email) GetSender() *parsemail.User {
|
||||
var ret *parsemail.User
|
||||
json.Unmarshal([]byte(d.Sender), &ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d Email) GetBcc() []*parsemail.User {
|
||||
var ret []*parsemail.User
|
||||
json.Unmarshal([]byte(d.Bcc), &ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d Email) GetCc() []*parsemail.User {
|
||||
var ret []*parsemail.User
|
||||
json.Unmarshal([]byte(d.Cc), &ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d Email) GetAttachments() []*parsemail.Attachment {
|
||||
var ret []*parsemail.Attachment
|
||||
json.Unmarshal([]byte(d.Attachments), &ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (d Email) MarshalJSON() ([]byte, error) {
|
||||
type Alias Email
|
||||
|
||||
@ -78,3 +115,24 @@ func (d Email) MarshalJSON() ([]byte, error) {
|
||||
Attachments: showAtt,
|
||||
})
|
||||
}
|
||||
|
||||
func (d Email) ToTransObj() *parsemail.Email {
|
||||
|
||||
return &parsemail.Email{
|
||||
From: &parsemail.User{
|
||||
Name: d.FromName,
|
||||
EmailAddress: d.FromAddress,
|
||||
},
|
||||
To: d.GetTos(),
|
||||
Subject: d.Subject,
|
||||
Text: []byte(d.Text.String),
|
||||
HTML: []byte(d.Html.String),
|
||||
Sender: d.GetSender(),
|
||||
ReplyTo: d.GetReplyTo(),
|
||||
Bcc: d.GetBcc(),
|
||||
Cc: d.GetCc(),
|
||||
Attachments: d.GetAttachments(),
|
||||
Date: d.SendDate.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
}
|
||||
|
320
server/pop3_server/action.go
Normal file
320
server/pop3_server/action.go
Normal file
@ -0,0 +1,320 @@
|
||||
package pop3_server
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/Jinnrry/gopop"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cast"
|
||||
"pmail/db"
|
||||
"pmail/models"
|
||||
"pmail/services/detail"
|
||||
"pmail/utils/array"
|
||||
"pmail/utils/context"
|
||||
"pmail/utils/errors"
|
||||
"pmail/utils/id"
|
||||
"pmail/utils/password"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type action struct {
|
||||
}
|
||||
|
||||
func (a action) Custom(session *gopop.Session, cmd string, args []string) ([]string, error) {
|
||||
if session.Ctx == nil {
|
||||
tc := &context.Context{}
|
||||
tc.SetValue(context.LogID, id.GenLogID())
|
||||
session.Ctx = tc
|
||||
}
|
||||
|
||||
log.WithContext(session.Ctx).Warnf("not supported cmd request! cmd:%s args:%v", cmd, args)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a action) Capa(session *gopop.Session) ([]string, error) {
|
||||
if session.Ctx == nil {
|
||||
tc := &context.Context{}
|
||||
tc.SetValue(context.LogID, id.GenLogID())
|
||||
session.Ctx = tc
|
||||
}
|
||||
|
||||
if session.InTls {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: CAPA With Tls")
|
||||
} else {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: CAPA Without Tls")
|
||||
}
|
||||
|
||||
ret := []string{
|
||||
"USER",
|
||||
"PASS",
|
||||
"TOP",
|
||||
"APOP",
|
||||
"STAT",
|
||||
"UIDL",
|
||||
"LIST",
|
||||
"RETR",
|
||||
"DELE",
|
||||
"REST",
|
||||
"NOOP",
|
||||
"QUIT",
|
||||
}
|
||||
if !session.InTls {
|
||||
ret = append(ret, "STLS")
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (a action) User(session *gopop.Session, username string) error {
|
||||
if session.Ctx == nil {
|
||||
tc := &context.Context{}
|
||||
tc.SetValue(context.LogID, id.GenLogID())
|
||||
session.Ctx = tc
|
||||
}
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: USER, Args:%s", username)
|
||||
|
||||
infos := strings.Split(username, "@")
|
||||
if len(infos) > 1 {
|
||||
username = infos[0]
|
||||
}
|
||||
|
||||
log.WithContext(session.Ctx).Debugf("POP3 User %s", username)
|
||||
|
||||
session.User = username
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a action) Pass(session *gopop.Session, pwd string) error {
|
||||
if session.Ctx == nil {
|
||||
tc := &context.Context{}
|
||||
tc.SetValue(context.LogID, id.GenLogID())
|
||||
session.Ctx = tc
|
||||
}
|
||||
|
||||
log.WithContext(session.Ctx).Debugf("POP3 PASS %s , User:%s", pwd, session.User)
|
||||
|
||||
var user models.User
|
||||
|
||||
encodePwd := password.Encode(pwd)
|
||||
|
||||
err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? and password =?"), session.User, encodePwd)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||
}
|
||||
|
||||
if user.ID > 0 {
|
||||
session.Status = gopop.TRANSACTION
|
||||
|
||||
session.Ctx.(*context.Context).UserID = user.ID
|
||||
session.Ctx.(*context.Context).UserName = user.Name
|
||||
session.Ctx.(*context.Context).UserAccount = user.Account
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("password error")
|
||||
}
|
||||
|
||||
func (a action) Apop(session *gopop.Session, username, digest string) error {
|
||||
if session.Ctx == nil {
|
||||
tc := &context.Context{}
|
||||
tc.SetValue(context.LogID, id.GenLogID())
|
||||
session.Ctx = tc
|
||||
}
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: APOP, Args:%s,%s", username, digest)
|
||||
|
||||
infos := strings.Split(username, "@")
|
||||
if len(infos) > 1 {
|
||||
username = infos[0]
|
||||
}
|
||||
|
||||
log.WithContext(session.Ctx).Debugf("POP3 APOP %s %s", username, digest)
|
||||
|
||||
var user models.User
|
||||
|
||||
err := db.Instance.Get(&user, db.WithContext(session.Ctx.(*context.Context), "select * from user where account =? "), username)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||
}
|
||||
|
||||
if user.ID > 0 && digest == password.Md5Encode(user.Password) {
|
||||
session.User = username
|
||||
session.Status = gopop.TRANSACTION
|
||||
|
||||
session.Ctx.(*context.Context).UserID = user.ID
|
||||
session.Ctx.(*context.Context).UserName = user.Name
|
||||
session.Ctx.(*context.Context).UserAccount = user.Account
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("password error")
|
||||
|
||||
}
|
||||
|
||||
type statInfo struct {
|
||||
Num int64 `json:"num"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (a action) Stat(session *gopop.Session) (msgNum, msgSize int64, err error) {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: STAT")
|
||||
|
||||
var si statInfo
|
||||
err = db.Instance.Get(&si, db.WithContext(session.Ctx.(*context.Context), "select count(1) as `num`, sum(length(text)+length(html)) as `size` from email"))
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||
err = nil
|
||||
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT :0,0")
|
||||
return 0, 0, nil
|
||||
}
|
||||
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT : %d,%d", si.Num, si.Size)
|
||||
|
||||
return si.Num, si.Size, nil
|
||||
}
|
||||
|
||||
func (a action) Uidl(session *gopop.Session, msg string) ([]gopop.UidlItem, error) {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: UIDL ,Args:%s", msg)
|
||||
|
||||
reqId := cast.ToInt64(msg)
|
||||
if reqId > 0 {
|
||||
return []gopop.UidlItem{
|
||||
{
|
||||
Id: reqId,
|
||||
UnionId: msg,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var res []listItem
|
||||
|
||||
var err error
|
||||
var ssql string
|
||||
|
||||
ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id FROM email")
|
||||
err = db.Instance.Select(&res, ssql)
|
||||
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
|
||||
err = nil
|
||||
return []gopop.UidlItem{}, nil
|
||||
}
|
||||
ret := []gopop.UidlItem{}
|
||||
for _, re := range res {
|
||||
ret = append(ret, gopop.UidlItem{
|
||||
Id: re.Id,
|
||||
UnionId: cast.ToString(re.Id),
|
||||
})
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type listItem struct {
|
||||
Id int64 `json:"id"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, error) {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: LIST ,Args:%s", msg)
|
||||
var res []listItem
|
||||
var listId int64
|
||||
if msg != "" {
|
||||
listId = cast.ToInt64(msg)
|
||||
if listId == 0 {
|
||||
return nil, errors.New("params error")
|
||||
}
|
||||
}
|
||||
var err error
|
||||
var ssql string
|
||||
|
||||
if listId != 0 {
|
||||
ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email where id =?")
|
||||
err = db.Instance.Select(&res, ssql, listId)
|
||||
} else {
|
||||
ssql = db.WithContext(session.Ctx.(*context.Context), "SELECT id, ifnull(LENGTH(TEXT) , 0) + ifnull(LENGTH(html) , 0) AS `size` FROM email")
|
||||
err = db.Instance.Select(&res, ssql)
|
||||
}
|
||||
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("SQL:%s Error: %+v", ssql, err)
|
||||
err = nil
|
||||
return []gopop.MailInfo{}, nil
|
||||
}
|
||||
ret := []gopop.MailInfo{}
|
||||
for _, re := range res {
|
||||
ret = append(ret, gopop.MailInfo{
|
||||
Id: re.Id,
|
||||
Size: re.Size,
|
||||
})
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (a action) Retr(session *gopop.Session, id int64) (string, int64, error) {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: RETR ,Args:%d", id)
|
||||
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
|
||||
if err != nil {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||
return "", 0, errors.New("server error")
|
||||
}
|
||||
|
||||
ret := email.ToTransObj().BuildBytes(session.Ctx.(*context.Context), false)
|
||||
return string(ret), cast.ToInt64(len(ret)), nil
|
||||
|
||||
}
|
||||
|
||||
func (a action) Delete(session *gopop.Session, id int64) error {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: DELE ,Args:%d", id)
|
||||
|
||||
session.DeleteIds = append(session.DeleteIds, id)
|
||||
session.DeleteIds = array.Unique(session.DeleteIds)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a action) Rest(session *gopop.Session) error {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: REST ")
|
||||
session.DeleteIds = []int64{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a action) Top(session *gopop.Session, id int64, n int) (string, error) {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: TOP %d %d", id, n)
|
||||
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
|
||||
if err != nil {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||
return "", errors.New("server error")
|
||||
}
|
||||
|
||||
ret := email.ToTransObj().BuildBytes(session.Ctx.(*context.Context), false)
|
||||
res := strings.Split(string(ret), "\n")
|
||||
headerEndLine := len(res) - 1
|
||||
for i, re := range res {
|
||||
if re == "\r" {
|
||||
headerEndLine = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(res) <= headerEndLine+n+1 {
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
return array.Join(res[0:headerEndLine+n+1], "\n"), nil
|
||||
|
||||
}
|
||||
|
||||
func (a action) Noop(session *gopop.Session) error {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: NOOP ")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a action) Quit(session *gopop.Session) error {
|
||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
|
||||
if len(session.DeleteIds) > 0 {
|
||||
|
||||
_, err := db.Instance.Exec(db.WithContext(session.Ctx.(*context.Context), "DELETE FROM email WHERE id in ?"), session.DeleteIds)
|
||||
if err != nil {
|
||||
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
57
server/pop3_server/pop3server.go
Normal file
57
server/pop3_server/pop3server.go
Normal file
@ -0,0 +1,57 @@
|
||||
package pop3_server
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"github.com/Jinnrry/gopop"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"pmail/config"
|
||||
"time"
|
||||
)
|
||||
|
||||
var instance *gopop.Server
|
||||
var instanceTls *gopop.Server
|
||||
|
||||
func StartWithTls() {
|
||||
crt, err := tls.LoadX509KeyPair(config.Instance.SSLPublicKeyPath, config.Instance.SSLPrivateKeyPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tlsConfig := &tls.Config{}
|
||||
tlsConfig.Certificates = []tls.Certificate{crt}
|
||||
tlsConfig.Time = time.Now
|
||||
tlsConfig.Rand = rand.Reader
|
||||
instanceTls = gopop.NewPop3Server(995, config.Instance.Domain, true, tlsConfig, action{})
|
||||
instanceTls.ConnectAliveTime = 5 * time.Minute
|
||||
|
||||
log.Infof("POP3 With TLS Server Start On Port :995")
|
||||
|
||||
err = instanceTls.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Start() {
|
||||
crt, err := tls.LoadX509KeyPair(config.Instance.SSLPublicKeyPath, config.Instance.SSLPrivateKeyPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tlsConfig := &tls.Config{}
|
||||
tlsConfig.Certificates = []tls.Certificate{crt}
|
||||
tlsConfig.Time = time.Now
|
||||
tlsConfig.Rand = rand.Reader
|
||||
instance = gopop.NewPop3Server(110, config.Instance.Domain, false, tlsConfig, action{})
|
||||
instance.ConnectAliveTime = 5 * time.Minute
|
||||
log.Infof("POP3 Server Start On Port :110")
|
||||
|
||||
err = instance.Start()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
instance.Stop()
|
||||
instanceTls.Stop()
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"pmail/dto/parsemail"
|
||||
"pmail/hooks"
|
||||
"pmail/http_server"
|
||||
"pmail/pop3_server"
|
||||
"pmail/session"
|
||||
"pmail/signal"
|
||||
"pmail/smtp_server"
|
||||
@ -37,9 +38,13 @@ func Init() {
|
||||
hooks.Init()
|
||||
// smtp server start
|
||||
go smtp_server.Start()
|
||||
go smtp_server.StartWithTLS()
|
||||
// http server start
|
||||
go http_server.HttpsStart()
|
||||
go http_server.HttpStart()
|
||||
// pop3 server start
|
||||
go pop3_server.Start()
|
||||
go pop3_server.StartWithTls()
|
||||
|
||||
configStr, _ := json.Marshal(config.Instance)
|
||||
log.Warnf("Config File Info: %s", configStr)
|
||||
@ -49,6 +54,7 @@ func Init() {
|
||||
smtp_server.Stop()
|
||||
http_server.HttpsStop()
|
||||
http_server.HttpStop()
|
||||
pop3_server.Stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func GenSSL(update bool) error {
|
||||
}
|
||||
|
||||
// CheckSSLCrtInfo 返回证书过期剩余天数
|
||||
func CheckSSLCrtInfo() (int, error) {
|
||||
func CheckSSLCrtInfo() (int, time.Time, error) {
|
||||
|
||||
cfg, err := setup.ReadConfig()
|
||||
if err != nil {
|
||||
@ -152,21 +152,21 @@ func CheckSSLCrtInfo() (int, error) {
|
||||
// load cert and key by tls.LoadX509KeyPair
|
||||
tlsCert, err := tls.LoadX509KeyPair(cfg.SSLPublicKeyPath, cfg.SSLPrivateKeyPath)
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err)
|
||||
return -1, time.Now(), errors.Wrap(err)
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(tlsCert.Certificate[0])
|
||||
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err)
|
||||
return -1, time.Now(), errors.Wrap(err)
|
||||
}
|
||||
|
||||
// 检查过期时间
|
||||
hours := cert.NotAfter.Sub(time.Now()).Hours()
|
||||
|
||||
if hours <= 0 {
|
||||
return -1, errors.New("Certificate has expired")
|
||||
return -1, time.Now(), errors.New("Certificate has expired")
|
||||
}
|
||||
|
||||
return cast.ToInt(hours / 24), nil
|
||||
return cast.ToInt(hours / 24), cert.NotAfter, nil
|
||||
}
|
||||
|
@ -13,16 +13,18 @@ import (
|
||||
"pmail/dto/parsemail"
|
||||
"pmail/hooks"
|
||||
"pmail/services/rule"
|
||||
"pmail/utils/array"
|
||||
"pmail/utils/async"
|
||||
"pmail/utils/context"
|
||||
"pmail/utils/id"
|
||||
"pmail/utils/send"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *Session) Data(r io.Reader) error {
|
||||
ctx := &context.Context{}
|
||||
ctx.SetValue(context.LogID, id.GenLogID())
|
||||
|
||||
ctx := s.Ctx
|
||||
|
||||
log.WithContext(ctx).Debugf("收到邮件")
|
||||
|
||||
emailData, err := io.ReadAll(r)
|
||||
@ -44,19 +46,74 @@ func (s *Session) Data(r io.Reader) error {
|
||||
|
||||
log.WithContext(ctx).Infof("邮件原始内容: %s", emailData)
|
||||
|
||||
var dkimStatus, SPFStatus bool
|
||||
email := parsemail.NewEmailFromReader(s.To, bytes.NewReader(emailData))
|
||||
|
||||
// DKIM校验
|
||||
dkimStatus = parsemail.Check(bytes.NewReader(emailData))
|
||||
|
||||
email := parsemail.NewEmailFromReader(bytes.NewReader(emailData))
|
||||
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("邮件内容解析失败! Error : %v \n", err)
|
||||
if s.From != "" {
|
||||
from := parsemail.BuilderUser(s.From)
|
||||
if email.From == nil {
|
||||
email.From = from
|
||||
}
|
||||
if email.From.EmailAddress != from.EmailAddress {
|
||||
// 协议中的from和邮件内容中的from不匹配,当成垃圾邮件处理
|
||||
log.WithContext(s.Ctx).Infof("垃圾邮件,拒信")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
|
||||
// 判断是收信还是转发
|
||||
account, domain := email.From.GetDomainAccount()
|
||||
if array.InArray(domain, config.Instance.Domains) && s.Ctx.UserName == account {
|
||||
// 转发
|
||||
err := saveEmail(ctx, email, 1, true, true)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("Email Save Error %v", err)
|
||||
}
|
||||
|
||||
send.Send(ctx, email)
|
||||
|
||||
} else {
|
||||
// 收件
|
||||
|
||||
var dkimStatus, SPFStatus bool
|
||||
|
||||
// DKIM校验
|
||||
dkimStatus = parsemail.Check(bytes.NewReader(emailData))
|
||||
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("邮件内容解析失败! Error : %v \n", err)
|
||||
}
|
||||
|
||||
SPFStatus = spfCheck(s.RemoteAddress.String(), email.Sender, email.Sender.EmailAddress)
|
||||
|
||||
saveEmail(ctx, email, 0, SPFStatus, dkimStatus)
|
||||
|
||||
log.WithContext(ctx).Debugf("开始执行插件!")
|
||||
|
||||
as2 := async.New(ctx)
|
||||
for _, hook := range hooks.HookList {
|
||||
if hook == nil {
|
||||
continue
|
||||
}
|
||||
as2.WaitProcess(func(hk any) {
|
||||
hk.(hooks.EmailHook).ReceiveParseAfter(email)
|
||||
}, hook)
|
||||
}
|
||||
as2.Wait()
|
||||
|
||||
log.WithContext(ctx).Debugf("开始执行邮件规则!")
|
||||
// 执行邮件规则
|
||||
rs := rule.GetAllRules(ctx)
|
||||
for _, r := range rs {
|
||||
if rule.MatchRule(ctx, r, email) {
|
||||
rule.DoRule(ctx, r, email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveEmail(ctx *context.Context, email *parsemail.Email, emailType int, SPFStatus, dkimStatus bool) error {
|
||||
var dkimV, spfV int8
|
||||
if dkimStatus {
|
||||
dkimV = 1
|
||||
@ -75,35 +132,16 @@ func (s *Session) Data(r io.Reader) error {
|
||||
log.WithContext(ctx).Infoln("垃圾邮件,拒信")
|
||||
return nil
|
||||
}
|
||||
log.WithContext(ctx).Debugf("开始执行插件!")
|
||||
|
||||
as2 := async.New(ctx)
|
||||
for _, hook := range hooks.HookList {
|
||||
if hook == nil {
|
||||
continue
|
||||
}
|
||||
as2.WaitProcess(func(hk any) {
|
||||
hk.(hooks.EmailHook).ReceiveParseAfter(email)
|
||||
}, hook)
|
||||
}
|
||||
as2.Wait()
|
||||
|
||||
log.WithContext(ctx).Debugf("开始执行邮件规则!")
|
||||
// 执行邮件规则
|
||||
rs := rule.GetAllRules(ctx)
|
||||
for _, r := range rs {
|
||||
if rule.MatchRule(ctx, r, email) {
|
||||
rule.DoRule(ctx, r, email)
|
||||
}
|
||||
}
|
||||
log.WithContext(ctx).Debugf("开始入库!")
|
||||
|
||||
if email == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sql := "INSERT INTO email (send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,is_read,status,group_id) VALUES (?,?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
_, err = db.Instance.Exec(sql,
|
||||
sql := "INSERT INTO email (type, send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,is_read,status,group_id) VALUES (?,?,?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
_, err := db.Instance.Exec(sql,
|
||||
emailType,
|
||||
email.Date,
|
||||
email.Subject,
|
||||
json2string(email.ReplyTo),
|
||||
|
@ -44,6 +44,18 @@ func testInit() {
|
||||
|
||||
}
|
||||
|
||||
func TestNuisanace(t *testing.T) {
|
||||
testInit()
|
||||
|
||||
s := Session{
|
||||
RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
|
||||
}
|
||||
|
||||
data, _ := os.ReadFile("../docs/nuisance/demo.txt")
|
||||
s.Data(bytes.NewReader(data))
|
||||
|
||||
}
|
||||
|
||||
func TestSession_Data(t *testing.T) {
|
||||
testInit()
|
||||
s := Session{
|
||||
|
@ -2,10 +2,18 @@ package smtp_server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"github.com/emersion/go-smtp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"pmail/config"
|
||||
"pmail/db"
|
||||
"pmail/models"
|
||||
"pmail/utils/context"
|
||||
"pmail/utils/errors"
|
||||
"pmail/utils/id"
|
||||
"pmail/utils/password"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -13,27 +21,70 @@ import (
|
||||
type Backend struct{}
|
||||
|
||||
func (bkd *Backend) NewSession(conn *smtp.Conn) (smtp.Session, error) {
|
||||
|
||||
remoteAddress := conn.Conn().RemoteAddr()
|
||||
ctx := &context.Context{}
|
||||
ctx.SetValue(context.LogID, id.GenLogID())
|
||||
log.WithContext(ctx).Debugf("新SMTP连接")
|
||||
|
||||
return &Session{
|
||||
RemoteAddress: remoteAddress,
|
||||
Ctx: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// A Session is returned after EHLO.
|
||||
type Session struct {
|
||||
RemoteAddress net.Addr
|
||||
User string
|
||||
From string
|
||||
To []string
|
||||
Ctx *context.Context
|
||||
}
|
||||
|
||||
func (s *Session) AuthPlain(username, password string) error {
|
||||
return nil
|
||||
func (s *Session) AuthPlain(username, pwd string) error {
|
||||
log.WithContext(s.Ctx).Debugf("Auth %s %s", username, pwd)
|
||||
|
||||
s.User = username
|
||||
|
||||
var user models.User
|
||||
|
||||
encodePwd := password.Encode(pwd)
|
||||
|
||||
infos := strings.Split(username, "@")
|
||||
if len(infos) > 1 {
|
||||
username = infos[0]
|
||||
}
|
||||
|
||||
err := db.Instance.Get(&user, db.WithContext(s.Ctx, "select * from user where account =? and password =?"),
|
||||
username, encodePwd)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.Errorf("%+v", err)
|
||||
}
|
||||
|
||||
if user.ID > 0 {
|
||||
s.Ctx.UserAccount = user.Account
|
||||
s.Ctx.UserID = user.ID
|
||||
s.Ctx.UserName = user.Name
|
||||
|
||||
log.WithContext(s.Ctx).Debugf("Auth Success %+v", user)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.WithContext(s.Ctx).Debugf("登陆错误%s %s", username, pwd)
|
||||
return errors.New("password error")
|
||||
}
|
||||
|
||||
func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
|
||||
log.WithContext(s.Ctx).Debugf("Mail Success %+v %+v", from, opts)
|
||||
s.From = from
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) Rcpt(to string) error {
|
||||
log.WithContext(s.Ctx).Debugf("Rcpt Success %+v", to)
|
||||
|
||||
s.To = append(s.To, to)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -44,15 +95,17 @@ func (s *Session) Logout() error {
|
||||
}
|
||||
|
||||
var instance *smtp.Server
|
||||
var instanceTls *smtp.Server
|
||||
|
||||
func Start() {
|
||||
func StartWithTLS() {
|
||||
be := &Backend{}
|
||||
|
||||
instance = smtp.NewServer(be)
|
||||
|
||||
instance.Addr = ":25"
|
||||
instance.Addr = ":465"
|
||||
instance.Domain = config.Instance.Domain
|
||||
instance.ReadTimeout = 10 * time.Second
|
||||
instance.AuthDisabled = false
|
||||
instance.WriteTimeout = 10 * time.Second
|
||||
instance.MaxMessageBytes = 1024 * 1024
|
||||
instance.MaxRecipients = 50
|
||||
@ -67,7 +120,36 @@ func Start() {
|
||||
// Configure the TLS support
|
||||
instance.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
|
||||
|
||||
log.Println("Starting server at", instance.Addr)
|
||||
log.Println("Starting Smtp With SSL Server Port:", instance.Addr)
|
||||
if err := instance.ListenAndServeTLS(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Start() {
|
||||
be := &Backend{}
|
||||
|
||||
instance = smtp.NewServer(be)
|
||||
|
||||
instance.Addr = ":25"
|
||||
instance.Domain = config.Instance.Domain
|
||||
instance.ReadTimeout = 10 * time.Second
|
||||
instance.AuthDisabled = false
|
||||
instance.WriteTimeout = 10 * time.Second
|
||||
instance.MaxMessageBytes = 1024 * 1024
|
||||
instance.MaxRecipients = 50
|
||||
// force TLS for auth
|
||||
instance.AllowInsecureAuth = false
|
||||
// Load the certificate and key
|
||||
cer, err := tls.LoadX509KeyPair(config.Instance.SSLPublicKeyPath, config.Instance.SSLPrivateKeyPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
// Configure the TLS support
|
||||
instance.TLSConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
|
||||
|
||||
log.Println("Starting Smtp Server Port:", instance.Addr)
|
||||
if err := instance.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -77,4 +159,7 @@ func Stop() {
|
||||
if instance != nil {
|
||||
instance.Close()
|
||||
}
|
||||
if instanceTls != nil {
|
||||
instanceTls.Close()
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ func (c *Context) SetValue(key string, value any) {
|
||||
|
||||
}
|
||||
|
||||
func (c Context) GetValue(key string) any {
|
||||
func (c *Context) GetValue(key string) any {
|
||||
if c.values == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
|
||||
// Encode 对密码两次md5加盐
|
||||
func Encode(password string) string {
|
||||
encodePwd := md5Encode(md5Encode(password+"pmail") + "pmail2023")
|
||||
encodePwd := Md5Encode(Md5Encode(password+"pmail") + "pmail2023")
|
||||
return encodePwd
|
||||
}
|
||||
|
||||
func md5Encode(str string) string {
|
||||
func Md5Encode(str string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(str))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
|
@ -66,20 +66,11 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
|
||||
domain := domain
|
||||
tos := tos
|
||||
as.WaitProcess(func(p any) {
|
||||
|
||||
err := smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
|
||||
err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
|
||||
// smtps发送失败,尝试smtp
|
||||
err = smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTPS on 465 Send Success !")
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
|
||||
// 重新选取证书域名
|
||||
@ -88,21 +79,11 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
|
||||
if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
|
||||
if hostnameErr.Certificate != nil {
|
||||
certificateHostName := hostnameErr.Certificate.DNSNames
|
||||
|
||||
// 再使用smtps 465端口 尝试
|
||||
err = smtp.SendMailWithTls(domainMatch(domain.domain, certificateHostName), domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
|
||||
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
|
||||
// smtps发送失败,尝试smtp
|
||||
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTPS Send Success !")
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,7 +109,7 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
|
||||
|
||||
func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
||||
|
||||
b := e.BuildBytes(ctx)
|
||||
b := e.BuildBytes(ctx, true)
|
||||
|
||||
var to []*parsemail.User
|
||||
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
||||
@ -170,19 +151,11 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
||||
tos := tos
|
||||
as.WaitProcess(func(p any) {
|
||||
|
||||
err := smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
|
||||
err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
|
||||
// smtps发送失败,尝试smtp
|
||||
err = smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTPS Send Success !")
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
|
||||
// 重新选取证书域名
|
||||
@ -191,20 +164,12 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
||||
if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
|
||||
if hostnameErr.Certificate != nil {
|
||||
certificateHostName := hostnameErr.Certificate.DNSNames
|
||||
|
||||
err = smtp.SendMailWithTls(domainMatch(domain.domain, certificateHostName), domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
|
||||
// smtps发送失败,尝试smtp
|
||||
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTPS on 465 Send Error! Error:%+v", err)
|
||||
// smtps发送失败,尝试smtp
|
||||
err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err)
|
||||
} else {
|
||||
log.WithContext(ctx).Infof("SMTPS Send Success !")
|
||||
log.WithContext(ctx).Infof("SMTP Send Success !")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user