diff --git a/Makefile b/Makefile index 82c32f3..f5acc3a 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ clean: build_fe: cd fe && yarn && yarn build + rm -rf server/http_server/dist cd server && cp -rf ../fe/dist http_server build_server: diff --git a/fe/src/i18n/i18n.js b/fe/src/i18n/i18n.js index d973653..acdc58a 100644 --- a/fe/src/i18n/i18n.js +++ b/fe/src/i18n/i18n.js @@ -63,7 +63,8 @@ var lang = { "web_domain": "Web Domain", "dns_desc": "Please add the following information to your DNS records", "ssl_auto": "Automatically configure SSL certificates (recommended)", - "wait_desc": "HTTP challenge mode completes in approximately 1 minute.", + "wait_desc": "Please Wait.", + "dns_challenge_wait": "DNS propagation and cache refreshes take a long time, and a wait of 10-30 minutes is possible here.", "ssl_challenge_type":"Challenge Type", "ssl_auto_http":"Http Request", "ssl_auto_dns":"DNS Records", @@ -176,7 +177,8 @@ var zhCN = { "ssl_challenge_type":"验证方式", "ssl_manuallyf": "手动配置SSL证书", "challenge_typ_desc": "如果PMail直接使用80端口,建议使用HTTP验证方式。", - "wait_desc": "HTTP验证模式大约1分钟完成", + "wait_desc": "请稍等", + "dns_challenge_wait": "DNS传播和缓存刷新时间较长,此处可能等待10-30分钟", "ssl_key_path": "ssl key文件位置", "ssl_crt_path": "ssl crt文件位置", "group_settings": "分组", diff --git a/fe/src/views/ListView.vue b/fe/src/views/ListView.vue index 4ca3dcb..d314635 100644 --- a/fe/src/views/ListView.vue +++ b/fe/src/views/ListView.vue @@ -40,6 +40,14 @@ + + + ! + + + diff --git a/fe/src/views/SetupView.vue b/fe/src/views/SetupView.vue index f43831d..c75e1fc 100644 --- a/fe/src/views/SetupView.vue +++ b/fe/src/views/SetupView.vue @@ -133,16 +133,17 @@
- + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -313,7 +170,30 @@
- {{ +
+ + + + + + + + + + + +
+ + + {{ lang.next }} @@ -324,10 +204,11 @@ import { reactive, ref } from 'vue' import { ElMessage } from 'element-plus' import lang from '../i18n/i18n'; import axios from 'axios' - import { getCurrentInstance } from 'vue' const app = getCurrentInstance() const $http = app.appContext.config.globalProperties.$http +const waitDesc = ref(lang.wait_desc) + const adminSettings = reactive({ "account": "admin", @@ -349,18 +230,16 @@ const domainSettings = reactive({ const sslSettings = reactive({ "type": "0", - "provider": "", "challenge": "http", "key_path": "./config/ssl/private.key", "crt_path": "./config/ssl/public.crt", - "paramsList": {}, + "paramsList": [], }) -const dnsApiParams = reactive({}) const active = ref(0) const fullscreenLoading = ref(false) - +const dnsChecking = ref(false) const dnsInfos = ref([ ]) @@ -455,9 +334,9 @@ const getSSLConfig = () => { ElMessage.error(res.errorMsg) } else { sslSettings.type = res.data.type - if (sslSettings.type == "2"){ + if (sslSettings.type == "2") { sslSettings.type = "0" - sslSettings.challenge="dns" + sslSettings.challenge = "dns" } @@ -475,24 +354,6 @@ const setSSLConfig = () => { sslType = "2" } - if (sslType == "2") { - - let params = { "action": "setParams", "step": "ssl", }; - - params = Object.assign(params, dnsApiParams); - - // dns验证方式先提交DNS api Key - $http.post("/api/setup", params).then((res) => { - if (res.errorNo != 0) { - fullscreenLoading.value = false; - ElMessage.error(res.errorMsg); - return; - } - }) - - - } - $http.post("/api/setup", { @@ -500,15 +361,18 @@ const setSSLConfig = () => { "step": "ssl", "ssl_type": sslType, "key_path": sslSettings.key_path, - "crt_path": sslSettings.crt_path, - "serviceName": sslSettings.provider + "crt_path": sslSettings.crt_path }).then((res) => { if (res.errorNo != 0) { fullscreenLoading.value = false; ElMessage.error(res.errorMsg) } else { + if (sslType == 2) { + fullscreenLoading.value = false; + dnsChecking.value = true; + getSSLDNSParams(); + } checkStatus(); - } }) } @@ -542,16 +406,23 @@ const setDomainConfig = () => { }) } -const provide_change = () => { - console.log(sslSettings.provider) - $http.post("/api/setup", { "action": "getParams", "step": "ssl", "serverName": sslSettings.provider }).then((res) => { +const getSSLDNSParams = () => { + $http.post("/api/setup", { "action": "getParams", "step": "ssl" }).then((res) => { if (res.errorNo != 0) { ElMessage.error(res.errorMsg) } else { sslSettings.paramsList = res.data + console.log(sslSettings.paramsList) } }) + if (sslSettings.paramsList.length == 0) { + setTimeout(function () { + getSSLDNSParams() + }, 1000); + } + + } @@ -576,8 +447,12 @@ const next = () => { active.value++ break case 5: - setSSLConfig(); - active.value++ + if (dnsChecking.value) { + fullscreenLoading.value = true; + waitDesc.value = lang.dns_challenge_wait; + } else { + setSSLConfig(); + } break } diff --git a/server/config/config.go b/server/config/config.go index 3423073..df4ccec 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -1,7 +1,12 @@ package config import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" "encoding/json" + "encoding/pem" "os" ) @@ -48,8 +53,8 @@ func (c *Config) SetSetupPort(setupPort int) { const DBTypeMySQL = "mysql" const DBTypeSQLite = "sqlite" const SSLTypeAutoHTTP = "0" //自动生成证书 -// const SSLTypeAutoDNS = "2" //自动生成证书,DNS api验证 -const SSLTypeUser = "1" //用户上传证书 +const SSLTypeAutoDNS = "2" //自动生成证书,DNS api验证 +const SSLTypeUser = "1" //用户上传证书 var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite} @@ -86,3 +91,33 @@ func Init() { } } + +func ReadPrivateKey() (*ecdsa.PrivateKey, bool) { + key, err := os.ReadFile("./config/ssl/account_private.pem") + if err != nil { + return createNewPrivateKey(), true + } + + block, _ := pem.Decode(key) + x509Encoded := block.Bytes + privateKey, _ := x509.ParseECPrivateKey(x509Encoded) + + return privateKey, false +} + +func createNewPrivateKey() *ecdsa.PrivateKey { + // Create a user. New accounts need an email and private key to start. + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(err) + } + x509Encoded, _ := x509.MarshalECPrivateKey(privateKey) + + // 将ec 密钥写入到 pem文件里 + keypem, _ := os.OpenFile("./config/ssl/account_private.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + err = pem.Encode(keypem, &pem.Block{Type: "EC PRIVATE KEY", Bytes: x509Encoded}) + if err != nil { + panic(err) + } + return privateKey +} diff --git a/server/config/config.json b/server/config/config.json index 54b48a7..36ad3aa 100644 --- a/server/config/config.json +++ b/server/config/config.json @@ -1,16 +1,17 @@ { - "logLevel": "debug", - "domain": "domain.com", - "webDomain": "mail.domain.com", + "logLevel": "", + "domain": "test.domain", + "domains": null, + "webDomain": "mail.test.domain", "dkimPrivateKeyPath": "config/dkim/dkim.priv", - "sslType": "0", - "SSLPrivateKeyPath": "config/ssl/private.key", - "SSLPublicKeyPath": "config/ssl/public.crt", - "dbDSN": "./config/pmail.db", + "sslType": "1", + "SSLPrivateKeyPath": "./config/ssl/private.key", + "SSLPublicKeyPath": "./config/ssl/public.crt", + "dbDSN": "./config/pmail_temp.db", "dbType": "sqlite", - "spamFilterLevel": 1, + "httpsEnabled": 1, + "spamFilterLevel": 0, "httpPort": 80, "httpsPort": 443, - "isInit": true, - "httpsEnabled": 1 + "isInit": true } \ No newline at end of file diff --git a/server/config/ssl/private.key b/server/config/ssl/private.key index 6e7bf48..6428258 100644 --- a/server/config/ssl/private.key +++ b/server/config/ssl/private.key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- diff --git a/server/config/ssl/public.crt b/server/config/ssl/public.crt index c2853d9..28db02a 100644 --- a/server/config/ssl/public.crt +++ b/server/config/ssl/public.crt @@ -1,61 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIFFDCCA/ygAwIBAgISBNoMwrAFQkkwJmbpDuLTLHLiMA0GCSqGSIb3DQEBCwUA -MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD -EwJSMzAeFw0yNDA0MTUwNTQ3MzlaFw0yNDA3MTQwNTQ3MzhaMBwxGjAYBgNVBAMT -EXNtdHAuamlhbmd3ZWkub25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAycVukHU+lPXo01d0W8ok0pxfy8/+tHKqd6gd9g1J6EOpf2SUvR77+URpKNXx -MokVX84VHnBGxDMvh5hM8oN5gqbJEwhTqOwZi19MQVfI5a1wrkYep1+hcAS/MsLg -wqxFp7ABsH2oljQLLYsGZTs/+QooDcmE2K+Nnj2nM5VZ9ouGX2ANPFMvSm1yuADm -P4962xXEN730UfEamRTGsuU9U1g51cAUasblIf2RU9nKdEQsEihpApS45X2qayWg -o3YJ6BAaqbZKmnekeIWkjlhHNYudNVxIqvp5MzndMPCNjfz/+xEcVn3hS5vzJMZQ -eOPJjSes6E2Cr0X2cvXnQGUfzQIDAQABo4ICODCCAjQwDgYDVR0PAQH/BAQDAgWg -MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0G -A1UdDgQWBBS+FIEsLeo9FCo2y2tOmFUfhiv/LzAfBgNVHSMEGDAWgBQULrMXt1hW -y65QCUDmH6+dixTCxjBVBggrBgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6 -Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iu -b3JnLzBBBgNVHREEOjA4ghFtYWlsLmppYW5nd2VpLm9uZYIQcG9wLmppYW5nd2Vp -Lm9uZYIRc210cC5qaWFuZ3dlaS5vbmUwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEE -BgorBgEEAdZ5AgQCBIH1BIHyAPAAdgAZmBBxCfDWUi4wgNKeP2S7g24ozPkPUo7u -385KPxa0ygAAAY7ggunqAAAEAwBHMEUCIFEMkK6C5zyorCJEM2nZqH75nkl6KQjI -RUiwLpcoupL0AiEAtKRWHmGBfL+AtLkurTurZlFURZIsrTqrreOFzThnSHoAdgBI -sONr2qZHNA/lagL6nTDrHFIBy1bdLIHZu7+rOdiEcwAAAY7ggunmAAAEAwBHMEUC -IGqaf3PAFZnvoKac1ASRb9eRpaGp7m+x/+Z1siJYegCnAiEAsLQJO7QvX/dXe+Bq -oCH1QhEqDFNhQLCavqrUyTi1wQ0wDQYJKoZIhvcNAQELBQADggEBABlSBbOwICT5 -zE+U4vyaeU0ufVSyjT7ZbohpMAJh8WK7zG7xj/XgAA0EX2LB62NVk3/3u/GF3uz6 -HsuCYUTsKY3MmwWttmwqWIxkMJk57j18J5vsJXW/YwqOz+v6h3QcUhUZW7c8Kl9I -t9t20uTssCWJ2OLe3TtumjxKX9iqeQ5CD3GDLTJlKP3UJ6eFGN5E42JjnIxk9GH5 -yvqcPd9OHwDXbrA13Q6Xn7tdV8rzerGi/gGo18QvCtvH8wda/T+AUxRxXM9Ggit8 -ITBsP3PWuIDvECTdQ+Zbft9Ut6PxOBLSH3Gqtghe+Fn7XQpchODw+0LbLi1fOgrW -8O+lguPxZ9w= ------END CERTIFICATE----- - ------BEGIN CERTIFICATE----- -MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw -WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg -RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP -R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx -sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm -NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg -Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG -/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB -Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA -FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw -AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw -Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB -gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W -PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl -ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz -CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm -lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 -avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 -yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O -yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids -hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ -HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv -MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX -nLRbwHOoq7hHwg== +MIIDmTCCAoECFH0cnkhiRVka2ZAWxpjtD+JgCv2GMA0GCSqGSIb3DQEBCwUAMIGI +MQswCQYDVQQGEwJDTjETMBEGA1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBwwCQkox +DjAMBgNVBAoMBVBNYWlsMQ4wDAYDVQQLDAVQTWFpbDEWMBQGA1UEAwwNKi50ZXN0 +LmRvbWFpbjEfMB0GCSqGSIb3DQEJARYQdGVzdEB0ZXN0LmRvbWFpbjAeFw0yNDA3 +MDUwODA5MTVaFw0zNDA3MDMwODA5MTVaMIGIMQswCQYDVQQGEwJDTjETMBEGA1UE +CAwKU29tZS1TdGF0ZTELMAkGA1UEBwwCQkoxDjAMBgNVBAoMBVBNYWlsMQ4wDAYD +VQQLDAVQTWFpbDEWMBQGA1UEAwwNKi50ZXN0LmRvbWFpbjEfMB0GCSqGSIb3DQEJ +ARYQdGVzdEB0ZXN0LmRvbWFpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAPbn/CpeOKV04CUs57Okr8kluCEV4fitVYvRReDUZNwcrDNJCADRVPlZh+3l +f0JdReC68M2pbHrd0CLearARRQwPJnuNphdimF6iFVr3EoPtTDzzJFKW/YxDyQBD +rTFVVij7uwJ7QnFnJjREoEjS1w7ezpO/4UPd44YlEM63r+juYzVb7H4skJ3HzLmh +4t46EcaIxX6uOeHY7MDctAxrMGjfahfurhoAYHcHO7zgPNWM1Q5Yt9zQo8Kv0Y2L +vHxSUmZVrb4GHQhKDnvaoZJy80AA98As3ZJJiCmT/4oIPHb4c+9S/uDVqg4WHtc5 +pAFy+mPArnKYltRawkWI6Q17ZOsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAIl8L +nL+wICckIRTfO4K4+F+7UHgLI2iTYRic6Hsy3K0aPvZAdVJiOlz0qaEcce+bFzj7 +BZiHQWDgiPF4vNFqcmas4oFV+Au2K8AoYQFJrq+3dtiUaMStT7JkjNjci3C2NhPO +U7Gjq2OJx6IrJr7ECr2SFW4Sstw/h+s/mBfNl2BbE7kmT1xu/lyxpIiT7bYgSk/l +A3cJFE1JIoa7eUPpV4Kh0titwJnDYVfQmEeBNYivyeNwe4hiHtiZDamI6H7Wu95b +ldRRiFELoVs0GCn/ttIaSFvGUPeahn9rTNUUkjp4Un0RxuQFx8umQYl50zt1GZ1X +DICmWUwYYejqdXrrww== -----END CERTIFICATE----- diff --git a/server/config/ssl/server.csr b/server/config/ssl/server.csr new file mode 100644 index 0000000..bbe4828 --- /dev/null +++ b/server/config/ssl/server.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICzjCCAbYCAQAwgYgxCzAJBgNVBAYTAkNOMRMwEQYDVQQIDApTb21lLVN0YXRl +MQswCQYDVQQHDAJCSjEOMAwGA1UECgwFUE1haWwxDjAMBgNVBAsMBVBNYWlsMRYw +FAYDVQQDDA0qLnRlc3QuZG9tYWluMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QHRlc3Qu +ZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9uf8Kl44pXTg +JSzns6SvySW4IRXh+K1Vi9FF4NRk3BysM0kIANFU+VmH7eV/Ql1F4Lrwzalset3Q +It5qsBFFDA8me42mF2KYXqIVWvcSg+1MPPMkUpb9jEPJAEOtMVVWKPu7AntCcWcm +NESgSNLXDt7Ok7/hQ93jhiUQzrev6O5jNVvsfiyQncfMuaHi3joRxojFfq454djs +wNy0DGswaN9qF+6uGgBgdwc7vOA81YzVDli33NCjwq/RjYu8fFJSZlWtvgYdCEoO +e9qhknLzQAD3wCzdkkmIKZP/igg8dvhz71L+4NWqDhYe1zmkAXL6Y8CucpiW1FrC +RYjpDXtk6wIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAPSNiDkAGFhMWFzQns+a +6+ujoI8Lf7baN/LklEEMRV2xq5sonj72ZU4PJDAyNFVj+pCKDOH5mb0r5ceRKYx/ +HOlXxYvDhvk3t9mAGrRwG3UhzbCSbIIcvdKbU4FDGaRIzrSsvcv9fUDnw5fKTL61 +IRDNewABlCncsUYfHrXeuMtqdiWyZMfYjiHDunCCo/FbrG70q9LjMHT4zHl9LV8T +jnrQzX0UaxBgLYDPEJX+2fqaXObv1HHSWlgZ6Ov9eRRKN2oRkb+KIbaTnlQ7Z7Y9 +kzwla/WOKJDL5FA7275tVpC553+rglq/0Jy9Hiq71Sis9gnG8eTYsghN5FHZSMFT +a1s= +-----END CERTIFICATE REQUEST----- diff --git a/server/controllers/email/list.go b/server/controllers/email/list.go index 5b5c7b2..2d66f02 100644 --- a/server/controllers/email/list.go +++ b/server/controllers/email/list.go @@ -27,6 +27,7 @@ type emilItem struct { IsRead bool `json:"is_read"` Sender User `json:"sender"` Dangerous bool `json:"dangerous"` + Error string `json:"error"` } type User struct { @@ -83,6 +84,7 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) { IsRead: email.IsRead == 1, Sender: sender, 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 8141b8e..14d6ce4 100644 --- a/server/controllers/email/send.go +++ b/server/controllers/email/send.go @@ -213,11 +213,28 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) { if err != nil { log.WithContext(ctx).Errorf("sql Error :%+v", err) } + + ue := models.UserEmail{ + UserID: ctx.UserID, + EmailID: modelEmail.Id, + Status: 2, + IsRead: 1, + } + db.Instance.Insert(&ue) + } else { _, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1 where id = ? "), modelEmail.Id) if err != nil { log.WithContext(ctx).Errorf("sql Error :%+v", err) } + + ue := models.UserEmail{ + UserID: ctx.UserID, + EmailID: modelEmail.Id, + Status: 1, + IsRead: 1, + } + db.Instance.Insert(&ue) } }, nil) diff --git a/server/controllers/group.go b/server/controllers/group.go index 6ef9f81..d798a77 100644 --- a/server/controllers/group.go +++ b/server/controllers/group.go @@ -31,7 +31,7 @@ func GetUserGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request }, { Label: i18n.GetText(ctx.Lang, "outbox"), - Tag: dto.SearchTag{Type: 1, Status: 1}.ToString(), + Tag: dto.SearchTag{Type: 1, Status: -1}.ToString(), }, { Label: i18n.GetText(ctx.Lang, "sketch"), diff --git a/server/controllers/setup.go b/server/controllers/setup.go index daa4075..b1aadcd 100644 --- a/server/controllers/setup.go +++ b/server/controllers/setup.go @@ -5,6 +5,7 @@ import ( log "github.com/sirupsen/logrus" "io" "net/http" + "os" "pmail/config" "pmail/dto/response" "pmail/services/setup" @@ -134,40 +135,36 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) { return } - //if reqData["step"] == "ssl" && reqData["action"] == "getParams" { - // params, err := ssl.GetServerParamsList(reqData["serverName"]) - // if err != nil { - // response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w) - // return - // } - // response.NewSuccessResponse(params).FPrint(w) - // return - //} - - //if reqData["step"] == "ssl" && reqData["action"] == "setParams" { - // for key, v := range reqData { - // if key != "step" && key != "action" { - // ssl.SetDomainServerParams(key, v) - // } - // } - // response.NewSuccessResponse("Succ").FPrint(w) - // return - //} + if reqData["step"] == "ssl" && reqData["action"] == "getParams" { + dnsChallenge := ssl.GetDnsChallengeInstance() + + response.NewSuccessResponse(dnsChallenge.GetDNSSettings(ctx)).FPrint(w) + return + } if reqData["step"] == "ssl" && reqData["action"] == "set" { + keyPath := reqData["key_path"] + crtPath := reqData["crt_path"] + + _, err := os.Stat(keyPath) + if err != nil { + response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w) + return + } - serviceName, ok := reqData["serviceName"] - if !ok { - serviceName = "" + _, err = os.Stat(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"], serviceName) + + err = ssl.SetSSL(reqData["ssl_type"], reqData["key_path"], reqData["crt_path"]) if err != nil { response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w) return } - //if reqData["ssl_type"] == config.SSLTypeAutoHTTP || reqData["ssl_type"] == config.SSLTypeAutoDNS { - if reqData["ssl_type"] == config.SSLTypeAutoHTTP { + if reqData["ssl_type"] == config.SSLTypeAutoHTTP || reqData["ssl_type"] == config.SSLTypeAutoDNS { err = ssl.GenSSL(false) if err != nil { response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w) diff --git a/server/cron_server/ssl_update.go b/server/cron_server/ssl_update.go index 03a0359..a343219 100644 --- a/server/cron_server/ssl_update.go +++ b/server/cron_server/ssl_update.go @@ -22,7 +22,7 @@ func Start() { } } - if config.Instance.SSLType == "0" { + if config.Instance.SSLType == config.SSLTypeAutoHTTP || config.Instance.SSLType == config.SSLTypeAutoDNS { go sslUpdateLoop() } else { go sslCheck() @@ -45,6 +45,7 @@ func sslCheck() { log.Errorf("SSL Check Error! %+v", err) } if newExpTime != expiredTime { + expiredTime = newExpTime log.Infoln("SSL certificate had update! restarting") signal.RestartChan <- true } diff --git a/server/main_test.go b/server/main_test.go index aa31f94..7267100 100644 --- a/server/main_test.go +++ b/server/main_test.go @@ -72,6 +72,7 @@ func TestMaster(t *testing.T) { t.Run("testSendEmail", testSendEmail) time.Sleep(8 * time.Second) t.Run("testEmailList", testEmailList) + t.Run("testGetDetail", testGetEmailDetail) t.Run("testDelEmail", testDelEmail) t.Run("testSendEmail2User1", testSendEmail2User1) @@ -108,6 +109,23 @@ func testCheckRule(t *testing.T) { } } +func testGetEmailDetail(t *testing.T) { + ret, err := httpClient.Post(TestHost+"/api/email/detail", "application/json", strings.NewReader(`{ + "id":1 +}`)) + if err != nil { + t.Error(err) + } + data, err := readResponse(ret.Body) + if err != nil { + t.Error(err) + } + if data.ErrorNo != 0 { + t.Error("GetEmailDetail Error! ", data) + } + +} + func testCreateRule(t *testing.T) { ret, err := httpClient.Post(TestHost+"/api/rule/add", "application/json", strings.NewReader(`{ "name":"Move Group", @@ -703,6 +721,7 @@ func testSendEmail2User3(t *testing.T) { t.Logf("testSendEmail2User3 Success! Response: %+v", data) } + func testEmailList(t *testing.T) { ret, err := httpClient.Post(TestHost+"/api/email/list", "application/json", strings.NewReader(`{}`)) if err != nil { @@ -716,8 +735,9 @@ func testEmailList(t *testing.T) { t.Error("Get Email List Api Error!") } dt := data.Data.(map[string]interface{}) - if len(dt["list"].([]interface{})) == 0 { + if dt["list"] == nil || len(dt["list"].([]interface{})) == 0 { t.Error("Email List Is Empty!") + return } lst := dt["list"].([]interface{}) diff --git a/server/models/user_email.go b/server/models/user_email.go index e1f5353..5c1c11b 100644 --- a/server/models/user_email.go +++ b/server/models/user_email.go @@ -2,8 +2,8 @@ package models type UserEmail struct { ID int `xorm:"id int unsigned not null pk autoincr"` - UserID int `xorm:"user_id int not null unique('uid_eid') index comment('用户id')"` - EmailID int `xorm:"email_id not null unique('uid_eid') index comment('信件id')"` + UserID int `xorm:"user_id int not null index('idx_eid') index comment('用户id')"` + EmailID int `xorm:"email_id not null index('idx_eid') index comment('信件id')"` IsRead int8 `xorm:"is_read tinyint(1) comment('是否已读')" json:"is_read"` GroupId int `xorm:"group_id int notnull default(0) comment('分组id')'" json:"group_id"` Status int8 `xorm:"status tinyint(4) notnull default(0) comment('0未发送,1已发送,2发送失败,3删除')" json:"status"` // 0未发送,1已发送,2发送失败 3删除 diff --git a/server/services/detail/detail.go b/server/services/detail/detail.go index 67cd8b5..a6b598b 100644 --- a/server/services/detail/detail.go +++ b/server/services/detail/detail.go @@ -27,7 +27,7 @@ func GetEmailDetail(ctx *context.Context, id int, markRead bool) (*response.Emai //获取邮件内容 var email response.EmailResponseData - _, err = db.Instance.ID(id).Get(&email) + _, err = db.Instance.Select("*,1 as is_read").Table("email").Where("id=?", id).Get(&email) if err != nil { log.WithContext(ctx).Errorf("SQL error:%+v", err) return nil, err @@ -37,7 +37,7 @@ func GetEmailDetail(ctx *context.Context, id int, markRead bool) (*response.Emai if markRead && ue.IsRead == 0 { ue.IsRead = 1 - _, err = db.Instance.Update(&ue) + _, err = db.Instance.Where("id=?", ue.ID).Update(&ue) if err != nil { log.WithContext(ctx).Errorf("SQL error:%+v", err) } diff --git a/server/services/setup/ssl/challenge.go b/server/services/setup/ssl/challenge.go index f7aefac..5786619 100644 --- a/server/services/setup/ssl/challenge.go +++ b/server/services/setup/ssl/challenge.go @@ -1,5 +1,12 @@ package ssl +import ( + "github.com/go-acme/lego/v4/challenge/dns01" + log "github.com/sirupsen/logrus" + "pmail/utils/context" + "time" +) + type authInfo struct { Domain string Token string @@ -35,3 +42,61 @@ func GetHttpChallengeInstance() *HttpChallenge { } return instance } + +type DNSChallenge struct { + AuthInfo map[string]*authInfo +} + +var dnsInstance *DNSChallenge + +func GetDnsChallengeInstance() *DNSChallenge { + if dnsInstance == nil { + dnsInstance = &DNSChallenge{ + AuthInfo: map[string]*authInfo{}, + } + } + return dnsInstance +} + +func (h *DNSChallenge) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + log.Infof("Presenting challenge Info : %+v", info) + h.AuthInfo[token] = &authInfo{ + Domain: info.FQDN, + Token: token, + KeyAuth: info.Value, + } + log.Infof("SSL Log:%s %s %s", domain, token, keyAuth) + return nil +} + +func (h *DNSChallenge) CleanUp(domain, token, keyAuth string) error { + delete(h.AuthInfo, token) + return nil +} + +func (h *DNSChallenge) Timeout() (timeout, interval time.Duration) { + return 60 * time.Minute, 5 * time.Second +} + +type DNSItem struct { + Type string `json:"type"` + Host string `json:"host"` + Value string `json:"value"` + TTL int `json:"ttl"` + Tips string `json:"tips"` +} + +func (h *DNSChallenge) GetDNSSettings(ctx *context.Context) []*DNSItem { + ret := []*DNSItem{} + for _, info := range h.AuthInfo { + ret = append(ret, &DNSItem{ + Type: "TXT", + Host: info.Domain, + Value: info.KeyAuth, + TTL: 3600, + }) + } + + return ret +} diff --git a/server/services/setup/ssl/ssl.go b/server/services/setup/ssl/ssl.go index fa6c825..b6b7d3e 100644 --- a/server/services/setup/ssl/ssl.go +++ b/server/services/setup/ssl/ssl.go @@ -3,11 +3,10 @@ package ssl import ( "crypto" "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "crypto/tls" "crypto/x509" "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/challenge/dns01" log "github.com/sirupsen/logrus" "github.com/spf13/cast" "os" @@ -51,15 +50,13 @@ func GetSSL() string { return cfg.SSLType } -func SetSSL(sslType, priKey, crtKey, serviceName string) error { +func SetSSL(sslType, priKey, crtKey string) error { cfg, err := setup.ReadConfig() if err != nil { panic(err) } - //if sslType == config.SSLTypeAutoHTTP || sslType == config.SSLTypeUser || sslType == config.SSLTypeAutoDNS { - if sslType == config.SSLTypeAutoHTTP || sslType == config.SSLTypeUser { + if sslType == config.SSLTypeAutoHTTP || sslType == config.SSLTypeUser || sslType == config.SSLTypeAutoDNS { cfg.SSLType = sslType - //cfg.DomainServiceName = serviceName } else { return errors.New("SSL Type Error!") } @@ -67,6 +64,8 @@ func SetSSL(sslType, priKey, crtKey, serviceName string) error { if cfg.SSLType == config.SSLTypeUser { cfg.SSLPrivateKeyPath = priKey cfg.SSLPublicKeyPath = crtKey + // 手动设置证书的情况下后台地址默认不启用https + cfg.HttpsEnabled = 2 } err = setup.WriteConfig(cfg) @@ -77,28 +76,62 @@ func SetSSL(sslType, priKey, crtKey, serviceName string) error { return nil } -func GenSSL(update bool) error { +func renewCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config) error { - cfg, err := setup.ReadConfig() + myUser := MyUser{ + Email: "i@" + cfg.Domain, + key: privateKey, + } + + conf := lego.NewConfig(&myUser) + conf.UserAgent = "PMail" + conf.Certificate.KeyType = certcrypto.RSA2048 + + // A client facilitates communication with the CA server. + client, err := lego.NewClient(conf) + if err != nil { + return errors.Wrap(err) + } + + var reg *registration.Resource + + reg, err = client.Registration.ResolveAccountByKey() + if err != nil { + return errors.Wrap(err) + } + + myUser.Registration = reg + + request := certificate.ObtainRequest{ + Domains: []string{"smtp." + cfg.Domain, cfg.WebDomain, "pop." + cfg.Domain}, + Bundle: true, + } + + log.Infof("wait ssl renew") + certificates, err := client.Certificate.Obtain(request) + if err != nil { + panic(err) + } + err = os.WriteFile("./config/ssl/private.key", certificates.PrivateKey, 0666) if err != nil { panic(err) } - if !update { - privateFile, errpi := os.ReadFile(cfg.SSLPrivateKeyPath) - public, errpu := os.ReadFile(cfg.SSLPublicKeyPath) - // 当前存在证书数据,就不生成了 - if errpi == nil && errpu == nil && len(privateFile) > 0 && len(public) > 0 { - return nil - } + err = os.WriteFile("./config/ssl/public.crt", certificates.Certificate, 0666) + if err != nil { + panic(err) } - // Create a user. New accounts need an email and private key to start. - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + err = os.WriteFile("./config/ssl/issuerCert.crt", certificates.IssuerCertificate, 0666) if err != nil { - return errors.Wrap(err) + panic(err) } + return nil +} + +func generateCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config, newAccount bool) error { + myUser := MyUser{ Email: "i@" + cfg.Domain, key: privateKey, @@ -114,31 +147,32 @@ func GenSSL(update bool) error { return errors.Wrap(err) } - if cfg.SSLType == "0" { + if cfg.SSLType == config.SSLTypeAutoHTTP { err = client.Challenge.SetHTTP01Provider(GetHttpChallengeInstance()) if err != nil { return errors.Wrap(err) } + } else if cfg.SSLType == config.SSLTypeAutoDNS { + err = client.Challenge.SetDNS01Provider(GetDnsChallengeInstance(), dns01.AddDNSTimeout(60*time.Minute)) + if err != nil { + return errors.Wrap(err) + } } - //else if cfg.SSLType == "2" { - // err = os.Setenv(strings.ToUpper(cfg.DomainServiceName)+"_PROPAGATION_TIMEOUT", "900") - // if err != nil { - // log.Errorf("Set ENV Variable Error: %s", err.Error()) - // } - // dnspodProvider, err := dns.NewDNSChallengeProviderByName(cfg.DomainServiceName) - // if err != nil { - // return errors.Wrap(err) - // } - // err = client.Challenge.SetDNS01Provider(dnspodProvider, dns01.AddDNSTimeout(15*time.Minute)) - // if err != nil { - // return errors.Wrap(err) - // } - //} - - reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - if err != nil { - return errors.Wrap(err) + + var reg *registration.Resource + + if newAccount { + reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + if err != nil { + return errors.Wrap(err) + } + } else { + reg, err = client.Registration.ResolveAccountByKey() + if err != nil { + return errors.Wrap(err) + } } + myUser.Registration = reg request := certificate.ObtainRequest{ @@ -154,7 +188,7 @@ func GenSSL(update bool) error { if err != nil { panic(err) } - + log.Infof("证书校验通过!") err = os.WriteFile("./config/ssl/private.key", certificates.PrivateKey, 0666) if err != nil { panic(err) @@ -177,6 +211,31 @@ func GenSSL(update bool) error { return nil } +func GenSSL(update bool) error { + + cfg, err := setup.ReadConfig() + if err != nil { + panic(err) + } + + if !update { + privateFile, errpi := os.ReadFile(cfg.SSLPrivateKeyPath) + public, errpu := os.ReadFile(cfg.SSLPublicKeyPath) + // 当前存在证书数据,就不生成了 + if errpi == nil && errpu == nil && len(privateFile) > 0 && len(public) > 0 { + return nil + } + } + + privateKey, newAccount := config.ReadPrivateKey() + + if !update { + return generateCertificate(privateKey, cfg, newAccount) + } + + return renewCertificate(privateKey, cfg) +} + // CheckSSLCrtInfo 返回证书过期剩余天数 func CheckSSLCrtInfo() (int, time.Time, error) { @@ -207,7 +266,7 @@ func CheckSSLCrtInfo() (int, time.Time, error) { } func Update(needRestart bool) { - if config.Instance != nil && config.Instance.IsInit && config.Instance.SSLType == "0" { + if config.Instance != nil && config.Instance.IsInit && (config.Instance.SSLType == config.SSLTypeAutoHTTP || config.Instance.SSLType == config.SSLTypeAutoDNS) { days, _, err := CheckSSLCrtInfo() if days < 30 || err != nil { if err != nil { diff --git a/server/utils/send/send.go b/server/utils/send/send.go index ee156d0..82ed62c 100644 --- a/server/utils/send/send.go +++ b/server/utils/send/send.go @@ -76,24 +76,28 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er tos := tos as.WaitProcess(func(p any) { 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 !") - } - // 重新选取证书域名 + // 使用其他方式发送 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) + 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) + } + } + + // 证书错误,从新选取证书发送 if certificateErr, ok := err.(*tls.CertificateVerificationError); ok { - if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is { + // 单测使用 + if domain.domain == "localhost" { + err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, 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) - if err != nil { - log.WithContext(ctx).Warnf("SMTP Send Error! Error:%+v", err) - } else { - log.WithContext(ctx).Infof("SMTP Send Success !") - } } } } @@ -185,7 +189,10 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) { // 证书错误,从新选取证书发送 if certificateErr, ok := err.(*tls.CertificateVerificationError); ok { - if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is { + // 单测使用 + if domain.domain == "localhost" { + err = smtp.SendMailUnsafe("", domain.mxHost+":25", nil, e.From.EmailAddress, buildAddress(tos), b) + } else if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is { if hostnameErr.Certificate != nil { certificateHostName := hostnameErr.Certificate.DNSNames // 重新选取证书发送