diff --git a/.github/workflows/unitTest.yml b/.github/workflows/unitTest.yml
index 376204f..2c5491d 100644
--- a/.github/workflows/unitTest.yml
+++ b/.github/workflows/unitTest.yml
@@ -26,7 +26,10 @@ jobs:
MYSQL_DATABASE: pmail
MYSQL_ROOT_PASSWORD: githubTest
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
-
+ postgres:
+ image: postgres
+ env:
+ POSTGRESQL_PASSWORD: githubTest
container:
image: golang
env:
diff --git a/Makefile b/Makefile
index f5acc3a..8411efa 100644
--- a/Makefile
+++ b/Makefile
@@ -53,4 +53,7 @@ test:
export setup_port=17888 && cd server && go test -v ./...
test_mysql:
- export setup_port=17888 && cd server && go test -args "mysql" -v ./...
\ No newline at end of file
+ export setup_port=17888 && cd server && go test -args "mysql" -v ./...
+
+test_postgres:
+ export setup_port=17888 && cd server && go test -args "postgres" -v ./...
\ No newline at end of file
diff --git a/README.md b/README.md
index 0f9dee1..cb7b7b7 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ beautiful and cute Logo for this project!
> 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.
-
+* Support multi-domain, multi-user and complete support for sending and receiving e-mail.
# How to run
diff --git a/README_CN.md b/README_CN.md
index b593870..017f1c9 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -39,6 +39,9 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
只要支持pop3、smtp协议的邮件客户端均可使用
+### 6、多域名、多用户支持
+
+支持多域名、多用户且完整支持收发邮件
# 如何部署
diff --git a/docs/cn.gif b/docs/cn.gif
index 05e8f7d..ad9f93a 100644
Binary files a/docs/cn.gif and b/docs/cn.gif differ
diff --git a/docs/en.gif b/docs/en.gif
index 3f9e45c..051e228 100644
Binary files a/docs/en.gif and b/docs/en.gif differ
diff --git a/fe/src/i18n/i18n.js b/fe/src/i18n/i18n.js
index acdc58a..1a28bc8 100644
--- a/fe/src/i18n/i18n.js
+++ b/fe/src/i18n/i18n.js
@@ -19,9 +19,9 @@ var lang = {
"sender": "Sender",
"title": "Title",
"date": "Date",
- "to": "To:",
- "cc": "Cc:",
- "sender_desc": "Only the email prefix is required",
+ "to": "To",
+ "cc": "Cc",
+ "sender_desc": "Sender",
"to_desc": "Recipient's e-mail address",
"cc_desc": "Cc's e-mail address",
"send": "send",
@@ -44,6 +44,7 @@ var lang = {
"SetDomail": "Set Domain",
"setDNS": "Set DNS",
"setSSL": "Set SSL",
+ "dns_root_desc": "Fill in the \"@\" or empty, as determined by your domain name service provider.",
"setDatabase": "Set Database",
"setAdminPassword": "Set Password",
"admin_account": "Administrator Account",
@@ -57,10 +58,13 @@ var lang = {
"type": "Type",
"db_select_ph": "please select your database",
"mysql_dsn": "MySQL DSN",
+ "pg_dsn": "PostgreSQL DSN",
"sqlite_db_path": "Data File Path",
"domain_desc": "Set your domain infomation.",
"smtp_domain": "SMTP Domain",
"web_domain": "Web Domain",
+ "multi_domain_setting": "Multi-Domain Setting",
+ "multi_domain_setting_desc": "Bind this mailbox to multiple domains.",
"dns_desc": "Please add the following information to your DNS records",
"ssl_auto": "Automatically configure SSL certificates (recommended)",
"wait_desc": "Please Wait.",
@@ -89,8 +93,6 @@ var lang = {
"rule_do":"Do the following:",
"from":"From Email Address",
"subject":"Email Subject",
- "to":"Recipient's address",
- "cc":"Cc's address",
"content":"Email Content",
"equal":"Equal",
"regex":"Regex Match",
@@ -127,9 +129,9 @@ var zhCN = {
"sender": "发件人",
"title": "主题",
"date": "时间",
- "to": "收件人:",
- "cc": "抄送:",
- "sender_desc": "只需要邮箱前缀",
+ "to": "收件人",
+ "cc": "抄送",
+ "sender_desc": "发件人",
"to_desc": "接收人邮件地址",
"cc_desc": "抄送人邮箱地址",
"send": "发送",
@@ -150,6 +152,9 @@ var zhCN = {
"settings": "设置",
"security": "安全",
"SetDomail": "域名设置",
+ "dns_root_desc": "填入@或者空,不同域名服务商写法不同",
+ "multi_domain_setting": "多域名设置",
+ "multi_domain_setting_desc": "将此邮箱绑定到多个域名上",
"setDNS": "DNS设置",
"setSSL": "SSL设置",
"setDatabase": "数据库设置",
@@ -165,6 +170,7 @@ var zhCN = {
"type": "类型",
"db_select_ph": "请选择你的数据库",
"mysql_dsn": "MySQL DSN",
+ "pg_dsn": "PostgreSQL DSN",
"sqlite_db_path": "存储位置",
"domain_desc": "设置你的域名信息。",
"smtp_domain": "SMTP域名地址",
@@ -197,8 +203,6 @@ var zhCN = {
"rule_do":"执行操作:",
"from":"发件人地址",
"subject":"邮件主题",
- "to":"收件人地址",
- "cc":"抄送地址",
"content":"邮件内容",
"equal":"等于",
"regex":"正则匹配",
diff --git a/fe/src/views/EditerView.vue b/fe/src/views/EditerView.vue
index b523e8d..a1c9ae8 100644
--- a/fe/src/views/EditerView.vue
+++ b/fe/src/views/EditerView.vue
@@ -2,7 +2,15 @@
-
-
+
-
@@ -205,6 +229,11 @@ import { ElMessage } from 'element-plus'
import lang from '../i18n/i18n';
import axios from 'axios'
import { getCurrentInstance } from 'vue'
+import {
+ Plus
+} from '@element-plus/icons-vue'
+
+
const app = getCurrentInstance()
const $http = app.appContext.config.globalProperties.$http
const waitDesc = ref(lang.wait_desc)
@@ -225,7 +254,8 @@ const dbSettings = reactive({
const domainSettings = reactive({
"web_domain": "",
- "smtp_domain": ""
+ "smtp_domain": "",
+ "multi_domain": []
})
const sslSettings = reactive({
@@ -241,11 +271,15 @@ const active = ref(0)
const fullscreenLoading = ref(false)
const dnsChecking = ref(false)
-const dnsInfos = ref([
-])
+const dnsInfos = ref({})
const port = ref(80)
+
+const addDomain = () => {
+ domainSettings.multi_domain.push([])
+}
+
const setPassword = () => {
if (adminSettings.hadSeted) {
active.value++;
@@ -302,6 +336,7 @@ const getDomainConfig = () => {
} else {
domainSettings.web_domain = res.data.web_domain;
domainSettings.smtp_domain = res.data.smtp_domain;
+ domainSettings.multi_domain = res.data.domains;
}
})
}
@@ -385,7 +420,11 @@ const checkStatus = () => {
checkStatus()
}, 1000);
} else {
- window.location.href = "https://" + domainSettings.web_domain;
+ if(sslSettings.type == 1){
+ window.location.href = "http://" + domainSettings.web_domain;
+ }else{
+ window.location.href = "https://" + domainSettings.web_domain;
+ }
}
}).catch((error) => {
setTimeout(function () {
@@ -396,7 +435,13 @@ const checkStatus = () => {
const setDomainConfig = () => {
- $http.post("/api/setup", { "action": "set", "step": "domain", "web_domain": domainSettings.web_domain, "smtp_domain": domainSettings.smtp_domain }).then((res) => {
+ $http.post("/api/setup", {
+ "action": "set",
+ "step": "domain",
+ "web_domain": domainSettings.web_domain,
+ "smtp_domain": domainSettings.smtp_domain,
+ "multi_domain": domainSettings.multi_domain.join(",")
+ }).then((res) => {
if (res.errorNo != 0) {
ElMessage.error(res.errorMsg)
} else {
diff --git a/server/config/config.go b/server/config/config.go
index df4ccec..4d943b1 100644
--- a/server/config/config.go
+++ b/server/config/config.go
@@ -13,13 +13,12 @@ import (
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表示自动生成证书,HTTP挑战模式,1表示用户上传证书,2表示自动-DNS挑战模式
- //DomainServiceName string `json:"domainServerName"` // 域名服务商名称
+ 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表示自动生成证书,HTTP挑战模式,1表示用户上传证书,2表示自动-DNS挑战模式
SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
DbDSN string `json:"dbDSN"`
@@ -52,11 +51,12 @@ func (c *Config) SetSetupPort(setupPort int) {
const DBTypeMySQL = "mysql"
const DBTypeSQLite = "sqlite"
+const DBTypePostgres = "postgres"
const SSLTypeAutoHTTP = "0" //自动生成证书
const SSLTypeAutoDNS = "2" //自动生成证书,DNS api验证
const SSLTypeUser = "1" //用户上传证书
-var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite}
+var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite, DBTypePostgres}
var Instance *Config = &Config{}
diff --git a/server/controllers/email/delete.go b/server/controllers/email/delete.go
index 56816f1..de794ad 100644
--- a/server/controllers/email/delete.go
+++ b/server/controllers/email/delete.go
@@ -11,7 +11,8 @@ import (
)
type emailDeleteRequest struct {
- IDs []int `json:"ids"`
+ IDs []int64 `json:"ids"`
+ ForcedDel bool `json:"forcedDel"`
}
func EmailDelete(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
@@ -30,7 +31,7 @@ func EmailDelete(ctx *context.Context, w http.ResponseWriter, req *http.Request)
return
}
- err = del_email.DelEmail(ctx, reqData.IDs)
+ err = del_email.DelEmail(ctx, reqData.IDs, reqData.ForcedDel)
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
diff --git a/server/controllers/email/list.go b/server/controllers/email/list.go
index 2d66f02..92abe59 100644
--- a/server/controllers/email/list.go
+++ b/server/controllers/email/list.go
@@ -26,6 +26,7 @@ type emilItem struct {
Datetime string `json:"datetime"`
IsRead bool `json:"is_read"`
Sender User `json:"sender"`
+ To []User `json:"to"`
Dangerous bool `json:"dangerous"`
Error string `json:"error"`
}
@@ -76,6 +77,9 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var sender User
_ = json.Unmarshal([]byte(email.Sender), &sender)
+ var tos []User
+ _ = json.Unmarshal([]byte(email.To), &tos)
+
lst = append(lst, &emilItem{
ID: email.Id,
Title: email.Subject,
@@ -83,6 +87,7 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
Datetime: email.SendDate.Format("2006-01-02 15:04:05"),
IsRead: email.IsRead == 1,
Sender: sender,
+ To: tos,
Dangerous: email.SPFCheck == 0 && email.DKIMCheck == 0,
Error: email.Error.String,
})
diff --git a/server/controllers/email/send.go b/server/controllers/email/send.go
index 14d6ce4..7cc0b08 100644
--- a/server/controllers/email/send.go
+++ b/server/controllers/email/send.go
@@ -16,6 +16,7 @@ import (
"pmail/hooks/framework"
"pmail/i18n"
"pmail/models"
+ "pmail/utils/array"
"pmail/utils/async"
"pmail/utils/context"
"pmail/utils/send"
@@ -69,6 +70,14 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
return
}
+ if reqData.From.Email != "" {
+ infos := strings.Split(reqData.From.Email, "@")
+ if len(infos) != 2 || !array.InArray(infos[1], config.Instance.Domains) {
+ response.NewErrorResponse(response.ParamsError, "params error", "").FPrint(w)
+ return
+ }
+ }
+
if reqData.From.Email == "" && reqData.From.Name != "" {
reqData.From.Email = reqData.From.Name + "@" + config.Instance.Domain
}
diff --git a/server/controllers/group.go b/server/controllers/group.go
index d798a77..9eb8063 100644
--- a/server/controllers/group.go
+++ b/server/controllers/group.go
@@ -9,6 +9,7 @@ import (
"pmail/dto"
"pmail/dto/response"
"pmail/i18n"
+ "pmail/models"
"pmail/services/group"
"pmail/utils/array"
"pmail/utils/context"
@@ -66,17 +67,19 @@ func AddGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
log.WithContext(ctx).Errorf("%+v", err)
}
- res, err := db.Instance.Exec(db.WithContext(ctx, "insert into `group` (name,parent_id,user_id) values (?,?,?)"), reqData.Name, reqData.ParentId, ctx.UserID)
- if err != nil {
- response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
- return
+ var newGroup models.Group = models.Group{
+ Name: reqData.Name,
+ ParentId: reqData.ParentId,
+ UserId: ctx.UserID,
}
- id, err := res.LastInsertId()
+
+ _, err = db.Instance.Insert(&newGroup)
if err != nil {
response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
return
}
- response.NewSuccessResponse(id).FPrint(w)
+
+ response.NewSuccessResponse(newGroup.ID).FPrint(w)
}
type delGroupRequest struct {
diff --git a/server/controllers/login.go b/server/controllers/login.go
index b0970af..e279202 100644
--- a/server/controllers/login.go
+++ b/server/controllers/login.go
@@ -6,11 +6,13 @@ import (
log "github.com/sirupsen/logrus"
"io"
"net/http"
+ "pmail/config"
"pmail/db"
"pmail/dto/response"
"pmail/i18n"
"pmail/models"
"pmail/session"
+ "pmail/utils/array"
"pmail/utils/context"
"pmail/utils/errors"
"pmail/utils/password"
@@ -44,10 +46,16 @@ func Login(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if user.ID != 0 {
userStr, _ := json.Marshal(user)
session.Instance.Put(req.Context(), "user", string(userStr))
+
+ domains := config.Instance.Domains
+ domains = array.Difference(domains, []string{config.Instance.Domain})
+ domains = append([]string{config.Instance.Domain}, domains...)
+
response.NewSuccessResponse(map[string]any{
"account": user.Account,
"name": user.Name,
"is_admin": user.IsAdmin,
+ "domains": domains,
}).FPrint(w)
} else {
response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "aperror"), "").FPrint(w)
diff --git a/server/controllers/setup.go b/server/controllers/setup.go
index ed9eff2..7735af1 100644
--- a/server/controllers/setup.go
+++ b/server/controllers/setup.go
@@ -3,6 +3,7 @@ package controllers
import (
"encoding/json"
log "github.com/sirupsen/logrus"
+ "github.com/spf13/cast"
"io"
"net/http"
"os"
@@ -41,9 +42,8 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
var reqData map[string]string
err = json.Unmarshal(reqBytes, &reqData)
-
if err != nil {
- response.NewSuccessResponse("").FPrint(w)
+ response.NewErrorResponse(response.ServerError, "", err.Error()).FPrint(w)
return
}
@@ -62,7 +62,7 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
}
if reqData["step"] == "database" && reqData["action"] == "set" {
- err := setup.SetDatabaseSettings(ctx, reqData["db_type"], reqData["db_dsn"])
+ err := setup.SetDatabaseSettings(ctx, cast.ToString(reqData["db_type"]), cast.ToString(reqData["db_dsn"]))
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
@@ -83,7 +83,7 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
}
if reqData["step"] == "password" && reqData["action"] == "set" {
- err := setup.SetAdminPassword(ctx, reqData["account"], reqData["password"])
+ err := setup.SetAdminPassword(ctx, cast.ToString(reqData["account"]), cast.ToString(reqData["password"]))
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
@@ -93,20 +93,21 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
}
if reqData["step"] == "domain" && reqData["action"] == "get" {
- smtpDomain, webDomain, err := setup.GetDomainSettings()
+ smtpDomain, webDomain, domains, err := setup.GetDomainSettings()
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
}
- response.NewSuccessResponse(map[string]string{
+ response.NewSuccessResponse(map[string]any{
"smtp_domain": smtpDomain,
"web_domain": webDomain,
+ "domains": domains,
}).FPrint(w)
return
}
if reqData["step"] == "domain" && reqData["action"] == "set" {
- err := setup.SetDomainSettings(reqData["smtp_domain"], reqData["web_domain"])
+ err := setup.SetDomainSettings(cast.ToString(reqData["smtp_domain"]), cast.ToString(reqData["web_domain"]), reqData["multi_domain"])
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
@@ -148,20 +149,20 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
keyPath := reqData["key_path"]
crtPath := reqData["crt_path"]
- _, err := os.Stat(keyPath)
+ _, err := os.Stat(cast.ToString(keyPath))
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
}
- _, err = os.Stat(crtPath)
+ _, err = os.Stat(cast.ToString(crtPath))
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
}
}
- err = ssl.SetSSL(reqData["ssl_type"], reqData["key_path"], reqData["crt_path"])
+ err = ssl.SetSSL(cast.ToString(reqData["ssl_type"]), cast.ToString(reqData["key_path"]), cast.ToString(reqData["crt_path"]))
if err != nil {
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
return
diff --git a/server/controllers/user.go b/server/controllers/user.go
index a1b7954..2cbc184 100644
--- a/server/controllers/user.go
+++ b/server/controllers/user.go
@@ -7,9 +7,11 @@ import (
"io"
"math"
"net/http"
+ "pmail/config"
"pmail/db"
"pmail/dto/response"
"pmail/models"
+ "pmail/utils/array"
"pmail/utils/context"
"pmail/utils/password"
)
@@ -104,10 +106,16 @@ func UserList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
}
func Info(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
+
+ domains := config.Instance.Domains
+ domains = array.Difference(domains, []string{config.Instance.Domain})
+ domains = append([]string{config.Instance.Domain}, domains...)
+
response.NewSuccessResponse(map[string]any{
"account": ctx.UserAccount,
"name": ctx.UserName,
"is_admin": ctx.IsAdmin,
+ "domains": domains,
}).FPrint(w)
}
diff --git a/server/db/init.go b/server/db/init.go
index be86133..b1a2634 100644
--- a/server/db/init.go
+++ b/server/db/init.go
@@ -3,6 +3,7 @@ package db
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
+ _ "github.com/lib/pq"
log "github.com/sirupsen/logrus"
_ "modernc.org/sqlite"
"pmail/config"
@@ -27,6 +28,10 @@ func Init(version string) error {
Instance, err = xorm.NewEngine("sqlite", dsn)
Instance.SetMaxOpenConns(1)
Instance.SetMaxIdleConns(1)
+ case "postgres":
+ Instance, err = xorm.NewEngine("postgres", dsn)
+ Instance.SetMaxOpenConns(100)
+ Instance.SetMaxIdleConns(10)
default:
return errors.New("Database Type Error!")
}
diff --git a/server/go.mod b/server/go.mod
index fbd96c4..8b5d11f 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -5,20 +5,22 @@ go 1.22.0
require (
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885
+ github.com/alexedwards/scs/postgresstore v0.0.0-20240316134038-7e11d57e8885
github.com/alexedwards/scs/sqlite3store v0.0.0-20240316134038-7e11d57e8885
github.com/alexedwards/scs/v2 v2.8.0
github.com/emersion/go-message v0.18.1
github.com/emersion/go-msgauth v0.6.8
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
- github.com/emersion/go-smtp v0.21.2
- github.com/go-acme/lego/v4 v4.17.3
+ github.com/emersion/go-smtp v0.21.3
+ github.com/go-acme/lego/v4 v4.17.4
github.com/go-sql-driver/mysql v1.8.1
+ github.com/lib/pq v1.10.9
github.com/mileusna/spf v0.9.5
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cast v1.6.0
- golang.org/x/crypto v0.24.0
+ golang.org/x/crypto v0.25.0
golang.org/x/text v0.16.0
- modernc.org/sqlite v1.30.0
+ modernc.org/sqlite v1.30.1
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.9
)
@@ -27,14 +29,14 @@ require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/go-jose/go-jose/v4 v4.0.2 // indirect
+ github.com/go-jose/go-jose/v4 v4.0.3 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/miekg/dns v1.1.59 // indirect
+ github.com/miekg/dns v1.1.61 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
@@ -44,13 +46,13 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
- golang.org/x/mod v0.18.0 // indirect
- golang.org/x/net v0.26.0 // indirect
+ golang.org/x/mod v0.19.0 // indirect
+ golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
- golang.org/x/sys v0.21.0 // indirect
- golang.org/x/tools v0.22.0 // indirect
+ golang.org/x/sys v0.22.0 // indirect
+ golang.org/x/tools v0.23.0 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
- modernc.org/libc v1.52.1 // indirect
+ modernc.org/libc v1.54.4 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
diff --git a/server/go.sum b/server/go.sum
index cb6485e..ca2b8b0 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -6,6 +6,8 @@ github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea h1:GISNlu8fPa2K+aySm
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea/go.mod h1:xcI6e+jbXWN+T8EWOJtHbAku6pzNqyCHaFvzdeL1r2o=
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885 h1:C7QAamNjR5yz6di4KJWAKcnxueKBgq4L/JGXhlnu35w=
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885/go.mod h1:p8jK3D80sw1PFrCSdlcJF1O75bp55HqbgDyyCLM0FrE=
+github.com/alexedwards/scs/postgresstore v0.0.0-20240316134038-7e11d57e8885 h1:012heQQRqytD5mSoXNzhfoTQaoPj6iRMvKh9DlUScoI=
+github.com/alexedwards/scs/postgresstore v0.0.0-20240316134038-7e11d57e8885/go.mod h1:TDDdV/xnjj+/4zBQ9a2k+i2AbuAdY7SQjPUh5zoTZ3M=
github.com/alexedwards/scs/sqlite3store v0.0.0-20240316134038-7e11d57e8885 h1:+DCxWg/ojncqS+TGAuRUoV7OfG/S4doh0pcpAwEcow0=
github.com/alexedwards/scs/sqlite3store v0.0.0-20240316134038-7e11d57e8885/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
@@ -26,6 +28,8 @@ github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpz
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.21.2 h1:OLDgvZKuofk4em9fT5tFG5j4jE1/hXnX75UMvcrL4AA=
github.com/emersion/go-smtp v0.21.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
+github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY=
+github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -35,8 +39,12 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-acme/lego/v4 v4.17.3 h1:5our7Qdyik0abag40abdmQuytq97iweaNHFMT4pYDnQ=
github.com/go-acme/lego/v4 v4.17.3/go.mod h1:Ol6l04hnmavqVHKYS/ByhXXqE64x8yVYhomha82uAUk=
+github.com/go-acme/lego/v4 v4.17.4 h1:h0nePd3ObP6o7kAkndtpTzCw8shOZuWckNYeUQwo36Q=
+github.com/go-acme/lego/v4 v4.17.4/go.mod h1:dU94SvPNqimEeb7EVilGGSnS0nU1O5Exir0pQ4QFL4U=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
+github.com/go-jose/go-jose/v4 v4.0.3 h1:o8aphO8Hv6RPmH+GfzVuyf7YXSBibp+8YyHdOoDESGo=
+github.com/go-jose/go-jose/v4 v4.0.3/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
@@ -72,6 +80,9 @@ 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.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
@@ -80,6 +91,8 @@ github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
+github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
+github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
github.com/mileusna/spf v0.9.5 h1:P6cmaIBwrhZaP9stXMzGOtxe+gIu65OVbZCmrAv9rgU=
github.com/mileusna/spf v0.9.5/go.mod h1:o6IdTae6QptAbLgx/+ueXSTSpkG+f1cqLemQJSew8sI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -130,12 +143,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
+golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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=
@@ -148,6 +165,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -179,6 +198,8 @@ 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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
@@ -198,6 +219,8 @@ 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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
+golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
+golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -221,8 +244,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
+modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo=
modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM=
+modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
@@ -231,6 +256,8 @@ modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M=
modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ=
+modernc.org/libc v1.54.4 h1:eDr4WnANZv+aRBKNCDo4khJbaHpxoTNOxeXqpznSZyY=
+modernc.org/libc v1.54.4/go.mod h1:CH8KSvv67UxcGCOLizggw3Zi3yT+sUjLWysK/YeUnqk=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
@@ -241,6 +268,8 @@ modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM=
modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw=
+modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
+modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
diff --git a/server/main_test.go b/server/main_test.go
index 7267100..b305b9f 100644
--- a/server/main_test.go
+++ b/server/main_test.go
@@ -243,6 +243,7 @@ func testCreateUser(t *testing.T) {
t.Error(err)
}
if data.ErrorNo != 0 {
+ t.Error(data)
t.Error("Create User Api Error!")
}
@@ -311,6 +312,10 @@ func testDataBaseSet(t *testing.T) {
if array.InArray("mysql", argList) {
configData = `
{"action":"set","step":"database","db_type":"mysql","db_dsn":"root:githubTest@tcp(mysql:3306)/pmail?parseTime=True"}
+`
+ } else if array.InArray("postgres", argList) {
+ configData = `
+{"action":"set","step":"database","db_type":"postgres","db_dsn":"postgres://postgres:githubTest@127.0.0.1:5432/pmail?sslmode=disable"}
`
}
diff --git a/server/pop3_server/action.go b/server/pop3_server/action.go
index baa2f47..ff82a7f 100644
--- a/server/pop3_server/action.go
+++ b/server/pop3_server/action.go
@@ -312,7 +312,7 @@ func (a action) Noop(session *gopop.Session) error {
func (a action) Quit(session *gopop.Session) error {
log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
if len(session.DeleteIds) > 0 {
- del_email.DelEmailI64(session.Ctx.(*context.Context), session.DeleteIds)
+ del_email.DelEmail(session.Ctx.(*context.Context), session.DeleteIds, false)
}
return nil
diff --git a/server/services/del_email/del_email.go b/server/services/del_email/del_email.go
index 94e53b4..8f91929 100644
--- a/server/services/del_email/del_email.go
+++ b/server/services/del_email/del_email.go
@@ -1,50 +1,55 @@
package del_email
import (
- log "github.com/sirupsen/logrus"
+ "github.com/spf13/cast"
"pmail/consts"
"pmail/db"
"pmail/models"
"pmail/utils/context"
+ "xorm.io/xorm"
)
-import . "xorm.io/builder"
-func DelEmail(ctx *context.Context, ids []int) error {
-
- if len(ids) == 0 {
- return nil
- }
-
- where, params, err := ToSQL(Eq{"user_id": ctx.UserID}.And(Eq{"email_id": ids}))
-
- if err != nil {
- log.Errorf("del email err: %v", err)
+func DelEmail(ctx *context.Context, ids []int64, forcedDel bool) error {
+ session := db.Instance.NewSession()
+ defer session.Close()
+ if err := session.Begin(); err != nil {
return err
}
-
- _, err = db.Instance.Table(&models.UserEmail{}).Where(where, params...).Update(map[string]interface{}{"status": consts.EmailStatusDel})
- if err != nil {
- log.Errorf("del email err: %v", err)
+ for _, id := range ids {
+ err := deleteOne(ctx, session, cast.ToInt64(id), forcedDel)
+ if err != nil {
+ session.Rollback()
+ return err
+ }
}
- return err
+ return session.Commit()
}
-func DelEmailI64(ctx *context.Context, ids []int64) error {
+type num struct {
+ Num int `xorm:"num"`
+}
- if len(ids) == 0 {
- return nil
+func deleteOne(ctx *context.Context, session *xorm.Session, id int64, forcedDel bool) error {
+ if !forcedDel {
+ _, err := session.Table(&models.UserEmail{}).Where("email_id=? and user_id=?", id, ctx.UserID).Update(map[string]interface{}{"status": consts.EmailStatusDel})
+ return err
}
-
- where, params, err := ToSQL(Eq{"user_id": ctx.UserID}.And(Eq{"email_id": ids}))
-
+ // 先删除关联关系
+ var ue models.UserEmail
+ _, err := session.Table(&models.UserEmail{}).Where("email_id=? and user_id=?", id, ctx.UserID).Delete(&ue)
if err != nil {
- log.Errorf("del email err: %v", err)
return err
}
-
- _, err = db.Instance.Table(&models.UserEmail{}).Where(where, params...).Update(map[string]interface{}{"status": consts.EmailStatusDel})
+ // 检查email是否还有人有权限
+ var Num num
+ _, err = session.Table(&models.UserEmail{}).Select("count(1) as num").Where("email_id=? ", id).Get(&Num)
if err != nil {
- log.Errorf("del email err: %v", err)
+ return err
+ }
+ if Num.Num == 0 {
+ var email models.Email
+ _, err = session.Table(&email).Where("id=?", id).Delete(&email)
+
}
return err
}
diff --git a/server/services/list/list.go b/server/services/list/list.go
index 9c423da..b6ab1fe 100644
--- a/server/services/list/list.go
+++ b/server/services/list/list.go
@@ -72,7 +72,7 @@ func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword str
sql += " order by e.id desc"
if limit < 10000 {
- sql += fmt.Sprintf(" limit %d,%d ", offset, limit)
+ sql += fmt.Sprintf(" LIMIT %d OFFSET %d ", limit, offset)
}
return sql, sqlParams
diff --git a/server/services/list/list_test.go b/server/services/list/list_test.go
index bdf2c27..2cdd824 100644
--- a/server/services/list/list_test.go
+++ b/server/services/list/list_test.go
@@ -1,54 +1 @@
package list
-
-import (
- "pmail/dto"
- "pmail/utils/context"
- "reflect"
- "testing"
-)
-
-func Test_genSQL(t *testing.T) {
- type args struct {
- ctx *context.Context
- count bool
- tagInfo dto.SearchTag
- keyword string
- pop3List bool
- offset int
- limit int
- }
- tests := []struct {
- name string
- args args
- want string
- want1 []any
- }{
- {
- name: "Group搜索",
- args: args{
- ctx: &context.Context{
- UserID: 1,
- },
- count: false,
- tagInfo: dto.SearchTag{-1, -1, 2},
- keyword: "",
- pop3List: false,
- offset: 0,
- limit: 0,
- },
- want: "select e.*,ue.is_read from email e left join user_email ue on e.id=ue.email_id where ue.user_id = ? and ue.status != 3 and ue.group_id=? order by e.id desc limit 0,10 ",
- want1: []any{1, 2},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, got1 := genSQL(tt.args.ctx, tt.args.count, tt.args.tagInfo, tt.args.keyword, tt.args.pop3List, tt.args.offset, tt.args.limit)
- if got != tt.want {
- t.Errorf("genSQL() got = \n%v, want \n%v", got, tt.want)
- }
- if !reflect.DeepEqual(got1, tt.want1) {
- t.Errorf("genSQL() got1 = \n%v, want \n%v", got1, tt.want1)
- }
- })
- }
-}
diff --git a/server/services/setup/db.go b/server/services/setup/db.go
index ce039a0..b6f32fe 100644
--- a/server/services/setup/db.go
+++ b/server/services/setup/db.go
@@ -43,7 +43,14 @@ func GetAdminPassword(ctx *context.Context) (string, error) {
func SetAdminPassword(ctx *context.Context, account, pwd string) error {
encodePwd := password.Encode(pwd)
- _, err := db.Instance.Exec(db.WithContext(ctx, "INSERT INTO user (account, name, password,is_admin) VALUES (?, 'admin',?,1)"), account, encodePwd)
+ var user models.User = models.User{
+ Account: account,
+ Name: "admin",
+ Password: encodePwd,
+ IsAdmin: 1,
+ }
+
+ _, err := db.Instance.Insert(&user)
if err != nil {
return errors.Wrap(err)
}
diff --git a/server/services/setup/dns.go b/server/services/setup/dns.go
index 5c14c35..85e81c9 100644
--- a/server/services/setup/dns.go
+++ b/server/services/setup/dns.go
@@ -17,19 +17,24 @@ type DNSItem struct {
Tips string `json:"tips"`
}
-func GetDNSSettings(ctx *context.Context) ([]*DNSItem, error) {
+func GetDNSSettings(ctx *context.Context) (map[string][]*DNSItem, error) {
configData, err := ReadConfig()
if err != nil {
return nil, errors.Wrap(err)
}
- ret := []*DNSItem{
- {Type: "A", Host: "smtp", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
- {Type: "A", Host: "pop", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
- {Type: "A", Host: "", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
- {Type: "MX", Host: "", Value: fmt.Sprintf("smtp.%s", configData.Domain), TTL: 3600},
- {Type: "TXT", Host: "", Value: "v=spf1 a mx ~all", TTL: 3600},
- {Type: "TXT", Host: "default._domainkey", Value: auth.DkimGen(), TTL: 3600},
+ ret := make(map[string][]*DNSItem)
+
+ for _, domain := range configData.Domains {
+ ret[domain] = []*DNSItem{
+ {Type: "A", Host: "smtp", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
+ {Type: "A", Host: "pop", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
+ {Type: "A", Host: "@", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
+ {Type: "MX", Host: "@", Value: fmt.Sprintf("smtp.%s", domain), TTL: 3600},
+ {Type: "TXT", Host: "@", Value: "v=spf1 a mx ~all", TTL: 3600},
+ {Type: "TXT", Host: "default._domainkey", Value: auth.DkimGen(), TTL: 3600},
+ }
}
+
return ret, nil
}
diff --git a/server/services/setup/domain.go b/server/services/setup/domain.go
index 9d41fa3..d518803 100644
--- a/server/services/setup/domain.go
+++ b/server/services/setup/domain.go
@@ -1,19 +1,21 @@
package setup
import (
+ "pmail/utils/array"
"pmail/utils/errors"
+ "strings"
)
-func GetDomainSettings() (string, string, error) {
+func GetDomainSettings() (string, string, []string, error) {
configData, err := ReadConfig()
if err != nil {
- return "", "", errors.Wrap(err)
+ return "", "", []string{}, errors.Wrap(err)
}
- return configData.Domain, configData.WebDomain, nil
+ return configData.Domain, configData.WebDomain, array.Difference(configData.Domains, []string{configData.Domain}), nil
}
-func SetDomainSettings(smtpDomain, webDomain string) error {
+func SetDomainSettings(smtpDomain, webDomain, multiDomains string) error {
configData, err := ReadConfig()
if err != nil {
return errors.Wrap(err)
@@ -27,6 +29,17 @@ func SetDomainSettings(smtpDomain, webDomain string) error {
return errors.New("web domain must not empty!")
}
+ configData.Domains = []string{}
+
+ if multiDomains != "" {
+ domains := strings.Split(multiDomains, ",")
+ configData.Domains = domains
+ }
+
+ if !array.InArray(smtpDomain, configData.Domains) {
+ configData.Domains = append(configData.Domains, smtpDomain)
+ }
+
configData.Domain = smtpDomain
configData.WebDomain = webDomain
diff --git a/server/services/setup/ssl/ssl.go b/server/services/setup/ssl/ssl.go
index b6b7d3e..447fc69 100644
--- a/server/services/setup/ssl/ssl.go
+++ b/server/services/setup/ssl/ssl.go
@@ -102,8 +102,14 @@ func renewCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config) error {
myUser.Registration = reg
+ domains := []string{cfg.WebDomain}
+ for _, domain := range cfg.Domains {
+ domains = append(domains, "smtp."+domain)
+ domains = append(domains, "pop."+domain)
+ }
+
request := certificate.ObtainRequest{
- Domains: []string{"smtp." + cfg.Domain, cfg.WebDomain, "pop." + cfg.Domain},
+ Domains: domains,
Bundle: true,
}
@@ -175,8 +181,14 @@ func generateCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config, newAc
myUser.Registration = reg
+ domains := []string{cfg.WebDomain}
+ for _, domain := range cfg.Domains {
+ domains = append(domains, "smtp."+domain)
+ domains = append(domains, "pop."+domain)
+ }
+
request := certificate.ObtainRequest{
- Domains: []string{"smtp." + cfg.Domain, cfg.WebDomain, "pop." + cfg.Domain},
+ Domains: domains,
Bundle: true,
}
diff --git a/server/session/init.go b/server/session/init.go
index e6c5fdd..b21fcad 100644
--- a/server/session/init.go
+++ b/server/session/init.go
@@ -2,6 +2,7 @@ package session
import (
"github.com/alexedwards/scs/mysqlstore"
+ "github.com/alexedwards/scs/postgresstore"
"github.com/alexedwards/scs/sqlite3store"
"github.com/alexedwards/scs/v2"
"pmail/config"
@@ -17,9 +18,16 @@ func Init() {
Instance.Lifetime = 7 * 24 * time.Hour
// 使用db存储session数据,目前为了架构简单,
// 暂不引入redis存储,如果日后性能存在瓶颈,可以将session迁移到redis
- if config.Instance.DbType == "mysql" {
+
+ switch config.Instance.DbType {
+ case config.DBTypeMySQL:
Instance.Store = mysqlstore.New(db.Instance.DB().DB)
- } else {
+ case config.DBTypeSQLite:
Instance.Store = sqlite3store.New(db.Instance.DB().DB)
+ case config.DBTypePostgres:
+ Instance.Store = postgresstore.New(db.Instance.DB().DB)
+ default:
+ panic("Unsupported database type: " + config.Instance.DbType)
}
+
}
diff --git a/server/utils/send/send.go b/server/utils/send/send.go
index 82ed62c..162914e 100644
--- a/server/utils/send/send.go
+++ b/server/utils/send/send.go
@@ -23,6 +23,7 @@ type mxDomain struct {
// Forward 转发邮件
func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) error {
+ _, fromDomain := e.From.GetDomainAccount()
log.WithContext(ctx).Debugf("开始转发邮件")
b := e.ForwardBuildBytes(ctx, forwardAddress)
@@ -75,16 +76,16 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
domain := domain
tos := tos
as.WaitProcess(func(p any) {
- err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
// 使用其他方式发送
if err != nil {
// EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
if errors.Is(err, smtp.NoSupportSTARTTLSError) || err.Error() == "EOF" {
- err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Warnf("Unsafe! %s Server Not Support SMTPS & STARTTLS", domain.domain)
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
}
}
@@ -92,12 +93,12 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
if certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
// 单测使用
if domain.domain == "localhost" {
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
} else if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
if hostnameErr.Certificate != nil {
certificateHostName := hostnameErr.Certificate.DNSNames
// 重新选取证书发送
- err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
}
}
}
@@ -121,6 +122,8 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
+ _, fromDomain := e.From.GetDomainAccount()
+
b := e.BuildBytes(ctx, true)
log.WithContext(ctx).Debugf("Message Infos : %s", string(b))
@@ -174,16 +177,16 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
tos := tos
as.WaitProcess(func(p any) {
- err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err := smtp.SendMail("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
// 使用其他方式发送
if err != nil {
// EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
if errors.Is(err, smtp.NoSupportSTARTTLSError) || err.Error() == "EOF" {
- err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMailWithTls("", domain.mxHost+":465", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
if err != nil {
log.WithContext(ctx).Warnf("Unsafe! %s Server Not Support SMTPS & STARTTLS", domain.domain)
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
}
}
@@ -191,12 +194,12 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
if certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
// 单测使用
if domain.domain == "localhost" {
- err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
} else if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
if hostnameErr.Certificate != nil {
certificateHostName := hostnameErr.Certificate.DNSNames
// 重新选取证书发送
- err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b)
+ err = smtp.SendMail(domainMatch(domain.domain, certificateHostName), domain.mxHost+":25", nil, e.From.EmailAddress, fromDomain, buildAddress(tos), b)
}
}
}
diff --git a/server/utils/smtp/smtp.go b/server/utils/smtp/smtp.go
index 36a9a6d..97536e3 100644
--- a/server/utils/smtp/smtp.go
+++ b/server/utils/smtp/smtp.go
@@ -16,7 +16,7 @@
//
// https://godoc.org/?q=smtp
//
-// 在go原始SMTP协议的基础上修复了TLS验证错误、支持了SMTPS协议
+// 在go原始SMTP协议的基础上修复了TLS验证错误、支持了SMTPS协议、 支持自定义HELLO命令的域名信息
package smtp
import (
@@ -58,17 +58,17 @@ type Client struct {
// Dial returns a new Client connected to an SMTP server at addr.
// The addr must include a port, as in "mail.example.com:smtp".
-func Dial(addr string) (*Client, error) {
+func Dial(addr, fromDomain string) (*Client, error) {
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(addr)
- return NewClient(conn, host)
+ return NewClient(conn, host, fromDomain)
}
// with tls
-func DialTls(addr, domain string) (*Client, error) {
+func DialTls(addr, domain, fromDomain string) (*Client, error) {
// TLS config
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
@@ -80,20 +80,24 @@ func DialTls(addr, domain string) (*Client, error) {
return nil, err
}
host, _, _ := net.SplitHostPort(addr)
- return NewClient(conn, host)
+ return NewClient(conn, host, fromDomain)
}
// NewClient returns a new Client using an existing connection and host as a
// server name to be used when authenticating.
-func NewClient(conn net.Conn, host string) (*Client, error) {
+func NewClient(conn net.Conn, host, fromDomain string) (*Client, error) {
text := textproto.NewConn(conn)
_, _, err := text.ReadResponse(220)
if err != nil {
text.Close()
return nil, err
}
+
localName := "domain.com"
- if config.Instance != nil && config.Instance.Domain != "" {
+
+ if fromDomain != "" {
+ localName = fromDomain
+ } else if config.Instance != nil && config.Instance.Domain != "" {
localName = config.Instance.Domain
}
@@ -333,7 +337,7 @@ func (c *Client) Data() (io.WriteCloser, error) {
return &dataCloser{c, c.Text.DotWriter()}, nil
}
-func SendMailWithTls(domain string, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+func SendMailWithTls(domain string, addr string, a smtp.Auth, from string, fromDomain string, to []string, msg []byte) error {
if err := validateLine(from); err != nil {
return err
}
@@ -342,7 +346,7 @@ func SendMailWithTls(domain string, addr string, a smtp.Auth, from string, to []
return err
}
}
- c, err := DialTls(addr, domain)
+ c, err := DialTls(addr, domain, fromDomain)
if err != nil {
return err
}
@@ -402,7 +406,7 @@ func SendMailWithTls(domain string, addr string, a smtp.Auth, from string, to []
// functionality. Higher-level packages exist outside of the standard
// library.
// 修复TSL验证问题
-func SendMail(domain string, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+func SendMail(domain string, addr string, a smtp.Auth, from string, fromDomain string, to []string, msg []byte) error {
if err := validateLine(from); err != nil {
return err
}
@@ -411,7 +415,7 @@ func SendMail(domain string, addr string, a smtp.Auth, from string, to []string,
return err
}
}
- c, err := Dial(addr)
+ c, err := Dial(addr, fromDomain)
if err != nil {
return err
}
@@ -465,7 +469,7 @@ func SendMail(domain string, addr string, a smtp.Auth, from string, to []string,
}
// SendMailUnsafe 无TLS加密的邮件发送方式
-func SendMailUnsafe(domain string, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
+func SendMailUnsafe(domain string, addr string, a smtp.Auth, from string, fromDomain string, to []string, msg []byte) error {
if err := validateLine(from); err != nil {
return err
}
@@ -474,7 +478,7 @@ func SendMailUnsafe(domain string, addr string, a smtp.Auth, from string, to []s
return err
}
}
- c, err := Dial(addr)
+ c, err := Dial(addr, fromDomain)
if err != nil {
return err
}