mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-02-20 11:43:09 +08:00
Merge branch 'v2.5.1'
This commit is contained in:
commit
e4c5ec57f9
1
Makefile
1
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:
|
||||
|
@ -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": "分组",
|
||||
|
@ -133,16 +133,17 @@
|
||||
<div class="form" width="600px">
|
||||
<el-form label-width="120px">
|
||||
<el-form-item :label="lang.type">
|
||||
<el-select :placeholder="lang.ssl_auto" v-model="sslSettings.type">
|
||||
<el-select :placeholder="lang.ssl_auto" v-model="sslSettings.type" :disabled="dnsChecking">
|
||||
<el-option :label="lang.ssl_auto" value="0" />
|
||||
<el-option :label="lang.ssl_manuallyf" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="lang.ssl_challenge_type" v-if="sslSettings.type == '0'">
|
||||
<el-select :placeholder="lang.ssl_auto_http" v-model="sslSettings.challenge">
|
||||
<el-select :placeholder="lang.ssl_auto_http" v-model="sslSettings.challenge"
|
||||
:disabled="dnsChecking">
|
||||
<el-option :label="lang.ssl_auto_http" value="http" />
|
||||
<!-- <el-option :label="lang.ssl_auto_dns" value="dns" /> -->
|
||||
<el-option :label="lang.ssl_auto_dns" value="dns" />
|
||||
</el-select>
|
||||
|
||||
<el-tooltip class="box-item" effect="dark" :content="lang.challenge_typ_desc"
|
||||
@ -151,153 +152,9 @@
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="lang.oomain_service_provider"
|
||||
v-if="sslSettings.type == '0' && sslSettings.challenge == 'dns'">
|
||||
<el-select @change="provide_change" :placeholder="lang.oomain_service_provider"
|
||||
v-model="sslSettings.provider">
|
||||
<el-option label="acme-dns" value="acme-dns" />
|
||||
<el-option label="alidns" value="alidns" />
|
||||
<el-option label="allinkl" value="allinkl" />
|
||||
<el-option label="arvancloud" value="arvancloud" />
|
||||
<el-option label="azure" value="azure" />
|
||||
<el-option label="azuredns" value="azuredns" />
|
||||
<el-option label="auroradns" value="auroradns" />
|
||||
<el-option label="autodns" value="autodns" />
|
||||
<el-option label="bindman" value="bindman" />
|
||||
<el-option label="bluecat" value="bluecat" />
|
||||
<el-option label="brandit" value="brandit" />
|
||||
<el-option label="bunny" value="bunny" />
|
||||
<el-option label="checkdomain" value="checkdomain" />
|
||||
<el-option label="civo" value="civo" />
|
||||
<el-option label="clouddns" value="clouddns" />
|
||||
<el-option label="cloudflare" value="cloudflare" />
|
||||
<el-option label="cloudns" value="cloudns" />
|
||||
<el-option label="cloudru" value="cloudru" />
|
||||
<el-option label="cloudxns" value="cloudxns" />
|
||||
<el-option label="conoha" value="conoha" />
|
||||
<el-option label="constellix" value="constellix" />
|
||||
<el-option label="cpanel" value="cpanel" />
|
||||
<el-option label="derak" value="derak" />
|
||||
<el-option label="desec" value="desec" />
|
||||
<el-option label="designate" value="designate" />
|
||||
<el-option label="digitalocean" value="digitalocean" />
|
||||
<el-option label="dnshomede" value="dnshomede" />
|
||||
<el-option label="dnsimple" value="dnsimple" />
|
||||
<el-option label="dnsmadeeasy" value="dnsmadeeasy" />
|
||||
<el-option label="dnspod" value="dnspod" />
|
||||
<el-option label="dode" value="dode" />
|
||||
<el-option label="domeneshop" value="domeneshop" />
|
||||
<el-option label="domainnameshop" value="domainnameshop" />
|
||||
<el-option label="dreamhost" value="dreamhost" />
|
||||
<el-option label="duckdns" value="duckdns" />
|
||||
<el-option label="dyn" value="dyn" />
|
||||
<el-option label="dynu" value="dynu" />
|
||||
<el-option label="easydns" value="easydns" />
|
||||
<el-option label="edgedns" value="edgedns" />
|
||||
<el-option label="fastdns" value="fastdns" />
|
||||
<el-option label="efficientip" value="efficientip" />
|
||||
<el-option label="epik" value="epik" />
|
||||
<el-option label="exec" value="exec" />
|
||||
<el-option label="exoscale" value="exoscale" />
|
||||
<el-option label="freemyip" value="freemyip" />
|
||||
<el-option label="gandi" value="gandi" />
|
||||
<el-option label="gandiv5" value="gandiv5" />
|
||||
<el-option label="gcloud" value="gcloud" />
|
||||
<el-option label="gcore" value="gcore" />
|
||||
<el-option label="glesys" value="glesys" />
|
||||
<el-option label="godaddy" value="godaddy" />
|
||||
<el-option label="googledomains" value="googledomains" />
|
||||
<el-option label="hetzner" value="hetzner" />
|
||||
<el-option label="hostingde" value="hostingde" />
|
||||
<el-option label="hosttech" value="hosttech" />
|
||||
<el-option label="httpreq" value="httpreq" />
|
||||
<el-option label="hurricane" value="hurricane" />
|
||||
<el-option label="hyperone" value="hyperone" />
|
||||
<el-option label="ibmcloud" value="ibmcloud" />
|
||||
<el-option label="iij" value="iij" />
|
||||
<el-option label="iijdpf" value="iijdpf" />
|
||||
<el-option label="infoblox" value="infoblox" />
|
||||
<el-option label="infomaniak" value="infomaniak" />
|
||||
<el-option label="internetbs" value="internetbs" />
|
||||
<el-option label="inwx" value="inwx" />
|
||||
<el-option label="ionos" value="ionos" />
|
||||
<el-option label="ipv64" value="ipv64" />
|
||||
<el-option label="iwantmyname" value="iwantmyname" />
|
||||
<el-option label="joker" value="joker" />
|
||||
<el-option label="liara" value="liara" />
|
||||
<el-option label="lightsail" value="lightsail" />
|
||||
<el-option label="linode" value="linode" />
|
||||
<el-option label="linodev4" value="linodev4" />
|
||||
<el-option label="liquidweb" value="liquidweb" />
|
||||
<el-option label="loopia" value="loopia" />
|
||||
<el-option label="luadns" value="luadns" />
|
||||
<el-option label="mailinabox" value="mailinabox" />
|
||||
<el-option label="manual" value="manual" />
|
||||
<el-option label="metaname" value="metaname" />
|
||||
<el-option label="mydnsjp" value="mydnsjp" />
|
||||
<el-option label="mythicbeasts" value="mythicbeasts" />
|
||||
<el-option label="namecheap" value="namecheap" />
|
||||
<el-option label="namedotcom" value="namedotcom" />
|
||||
<el-option label="namesilo" value="namesilo" />
|
||||
<el-option label="nearlyfreespeech" value="nearlyfreespeech" />
|
||||
<el-option label="netcup" value="netcup" />
|
||||
<el-option label="netlify" value="netlify" />
|
||||
<el-option label="nicmanager" value="nicmanager" />
|
||||
<el-option label="nifcloud" value="nifcloud" />
|
||||
<el-option label="njalla" value="njalla" />
|
||||
<el-option label="nodion" value="nodion" />
|
||||
<el-option label="ns1" value="ns1" />
|
||||
<el-option label="oraclecloud" value="oraclecloud" />
|
||||
<el-option label="otc" value="otc" />
|
||||
<el-option label="ovh" value="ovh" />
|
||||
<el-option label="pdns" value="pdns" />
|
||||
<el-option label="plesk" value="plesk" />
|
||||
<el-option label="porkbun" value="porkbun" />
|
||||
<el-option label="rackspace" value="rackspace" />
|
||||
<el-option label="rcodezero" value="rcodezero" />
|
||||
<el-option label="regru" value="regru" />
|
||||
<el-option label="rfc2136" value="rfc2136" />
|
||||
<el-option label="rimuhosting" value="rimuhosting" />
|
||||
<el-option label="route53" value="route53" />
|
||||
<el-option label="safedns" value="safedns" />
|
||||
<el-option label="sakuracloud" value="sakuracloud" />
|
||||
<el-option label="scaleway" value="scaleway" />
|
||||
<el-option label="selectel" value="selectel" />
|
||||
<el-option label="servercow" value="servercow" />
|
||||
<el-option label="shellrent" value="shellrent" />
|
||||
<el-option label="simply" value="simply" />
|
||||
<el-option label="sonic" value="sonic" />
|
||||
<el-option label="stackpath" value="stackpath" />
|
||||
<el-option label="tencentcloud" value="tencentcloud" />
|
||||
<el-option label="transip" value="transip" />
|
||||
<el-option label="ultradns" value="ultradns" />
|
||||
<el-option label="variomedia" value="variomedia" />
|
||||
<el-option label="vegadns" value="vegadns" />
|
||||
<el-option label="vercel" value="vercel" />
|
||||
<el-option label="versio" value="versio" />
|
||||
<el-option label="vinyldns" value="vinyldns" />
|
||||
<el-option label="vkcloud" value="vkcloud" />
|
||||
<el-option label="vscale" value="vscale" />
|
||||
<el-option label="vultr" value="vultr" />
|
||||
<el-option label="webnames" value="webnames" />
|
||||
<el-option label="websupport" value="websupport" />
|
||||
<el-option label="wedos" value="wedos" />
|
||||
<el-option label="yandex" value="yandex" />
|
||||
<el-option label="yandex360" value="yandex360" />
|
||||
<el-option label="yandexcloud" value="yandexcloud" />
|
||||
<el-option label="zoneee" value="zoneee" />
|
||||
<el-option label="zonomi" value="zonomi" />
|
||||
</el-select>
|
||||
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item :label="item"
|
||||
v-if="sslSettings.paramsList.length != 0 && sslSettings.type == 0 && sslSettings.challenge == 'dns'"
|
||||
v-for="item in sslSettings.paramsList">
|
||||
<el-input style="width: 240px" :placeholder="item" v-model="dnsApiParams[item]" />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="lang.ssl_key_path" v-if="sslSettings.type == '1'">
|
||||
<el-input placeholder="./config/ssl/private.key" v-model="sslSettings.key_path"></el-input>
|
||||
@ -313,7 +170,30 @@
|
||||
|
||||
</div>
|
||||
|
||||
<el-button :element-loading-text="lang.wait_desc" v-loading.fullscreen.lock="fullscreenLoading" id="next" style="margin-top: 12px" @click="next">{{
|
||||
<div v-if="dnsChecking">
|
||||
<label>{{ lang.dns_desc }}</label>
|
||||
<el-table :data="sslSettings.paramsList" border v-loading="sslSettings.paramsList.length == 0">
|
||||
<el-table-column prop="host" label="HOSTNAME" width="110px" />
|
||||
<el-table-column prop="type" label="TYPE" width="110px" />
|
||||
<el-table-column prop="value" label="VALUE">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center">
|
||||
<el-tooltip :content="scope.row.tips" placement="top" v-if="scope.row.tips != ''">
|
||||
{{ scope.row.value }}
|
||||
</el-tooltip>
|
||||
<span v-else>{{ scope.row.value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
<el-table-column prop="ttl" label="TTL" width="110px" />
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<el-button :element-loading-text="waitDesc" v-loading.fullscreen.lock="fullscreenLoading" id="next"
|
||||
style="margin-top: 12px" @click="next">{{
|
||||
lang.next }}</el-button>
|
||||
|
||||
</div>
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"] == "getParams" {
|
||||
dnsChallenge := ssl.GetDnsChallengeInstance()
|
||||
|
||||
//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
|
||||
//}
|
||||
response.NewSuccessResponse(dnsChallenge.GetDNSSettings(ctx)).FPrint(w)
|
||||
return
|
||||
}
|
||||
|
||||
if reqData["step"] == "ssl" && reqData["action"] == "set" {
|
||||
keyPath := reqData["key_path"]
|
||||
crtPath := reqData["crt_path"]
|
||||
|
||||
serviceName, ok := reqData["serviceName"]
|
||||
if !ok {
|
||||
serviceName = ""
|
||||
}
|
||||
err := ssl.SetSSL(reqData["ssl_type"], reqData["key_path"], reqData["crt_path"], serviceName)
|
||||
_, err := os.Stat(keyPath)
|
||||
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 {
|
||||
_, 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"])
|
||||
if err != nil {
|
||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,27 +76,7 @@ func SetSSL(sslType, priKey, crtKey, serviceName string) 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
|
||||
}
|
||||
}
|
||||
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
func renewCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config) error {
|
||||
|
||||
myUser := MyUser{
|
||||
Email: "i@" + cfg.Domain,
|
||||
@ -114,31 +93,86 @@ func GenSSL(update bool) error {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
if cfg.SSLType == "0" {
|
||||
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)
|
||||
}
|
||||
|
||||
err = os.WriteFile("./config/ssl/public.crt", certificates.Certificate, 0666)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.WriteFile("./config/ssl/issuerCert.crt", certificates.IssuerCertificate, 0666)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config, newAccount bool) error {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user