mirror of
https://github.com/Jinnrry/PMail.git
synced 2025-02-20 11:43:09 +08:00
parent
402e00150d
commit
172e5871ec
5
.github/workflows/unitTest.yml
vendored
5
.github/workflows/unitTest.yml
vendored
@ -26,7 +26,10 @@ jobs:
|
|||||||
MYSQL_DATABASE: pmail
|
MYSQL_DATABASE: pmail
|
||||||
MYSQL_ROOT_PASSWORD: githubTest
|
MYSQL_ROOT_PASSWORD: githubTest
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
env:
|
||||||
|
POSTGRESQL_PASSWORD: githubTest
|
||||||
container:
|
container:
|
||||||
image: golang
|
image: golang
|
||||||
env:
|
env:
|
||||||
|
5
Makefile
5
Makefile
@ -53,4 +53,7 @@ test:
|
|||||||
export setup_port=17888 && cd server && go test -v ./...
|
export setup_port=17888 && cd server && go test -v ./...
|
||||||
|
|
||||||
test_mysql:
|
test_mysql:
|
||||||
export setup_port=17888 && cd server && go test -args "mysql" -v ./...
|
export setup_port=17888 && cd server && go test -args "mysql" -v ./...
|
||||||
|
|
||||||
|
test_postgres:
|
||||||
|
export setup_port=17888 && cd server && go test -args "postgres" -v ./...
|
@ -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)
|
> 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 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
|
# How to run
|
||||||
|
@ -39,6 +39,9 @@ PMail是一个追求极简部署流程、极致资源占用的个人域名邮箱
|
|||||||
|
|
||||||
只要支持pop3、smtp协议的邮件客户端均可使用
|
只要支持pop3、smtp协议的邮件客户端均可使用
|
||||||
|
|
||||||
|
### 6、多域名、多用户支持
|
||||||
|
|
||||||
|
支持多域名、多用户且完整支持收发邮件
|
||||||
|
|
||||||
# 如何部署
|
# 如何部署
|
||||||
|
|
||||||
|
BIN
docs/cn.gif
BIN
docs/cn.gif
Binary file not shown.
Before Width: | Height: | Size: 963 KiB After Width: | Height: | Size: 1.8 MiB |
BIN
docs/en.gif
BIN
docs/en.gif
Binary file not shown.
Before Width: | Height: | Size: 1004 KiB After Width: | Height: | Size: 1.7 MiB |
@ -19,9 +19,9 @@ var lang = {
|
|||||||
"sender": "Sender",
|
"sender": "Sender",
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"to": "To:",
|
"to": "To",
|
||||||
"cc": "Cc:",
|
"cc": "Cc",
|
||||||
"sender_desc": "Only the email prefix is required",
|
"sender_desc": "Sender",
|
||||||
"to_desc": "Recipient's e-mail address",
|
"to_desc": "Recipient's e-mail address",
|
||||||
"cc_desc": "Cc's e-mail address",
|
"cc_desc": "Cc's e-mail address",
|
||||||
"send": "send",
|
"send": "send",
|
||||||
@ -44,6 +44,7 @@ var lang = {
|
|||||||
"SetDomail": "Set Domain",
|
"SetDomail": "Set Domain",
|
||||||
"setDNS": "Set DNS",
|
"setDNS": "Set DNS",
|
||||||
"setSSL": "Set SSL",
|
"setSSL": "Set SSL",
|
||||||
|
"dns_root_desc": "Fill in the \"@\" or empty, as determined by your domain name service provider.",
|
||||||
"setDatabase": "Set Database",
|
"setDatabase": "Set Database",
|
||||||
"setAdminPassword": "Set Password",
|
"setAdminPassword": "Set Password",
|
||||||
"admin_account": "Administrator Account",
|
"admin_account": "Administrator Account",
|
||||||
@ -57,10 +58,13 @@ var lang = {
|
|||||||
"type": "Type",
|
"type": "Type",
|
||||||
"db_select_ph": "please select your database",
|
"db_select_ph": "please select your database",
|
||||||
"mysql_dsn": "MySQL DSN",
|
"mysql_dsn": "MySQL DSN",
|
||||||
|
"pg_dsn": "PostgreSQL DSN",
|
||||||
"sqlite_db_path": "Data File Path",
|
"sqlite_db_path": "Data File Path",
|
||||||
"domain_desc": "Set your domain infomation.",
|
"domain_desc": "Set your domain infomation.",
|
||||||
"smtp_domain": "SMTP Domain",
|
"smtp_domain": "SMTP Domain",
|
||||||
"web_domain": "Web 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",
|
"dns_desc": "Please add the following information to your DNS records",
|
||||||
"ssl_auto": "Automatically configure SSL certificates (recommended)",
|
"ssl_auto": "Automatically configure SSL certificates (recommended)",
|
||||||
"wait_desc": "Please Wait.",
|
"wait_desc": "Please Wait.",
|
||||||
@ -89,8 +93,6 @@ var lang = {
|
|||||||
"rule_do":"Do the following:",
|
"rule_do":"Do the following:",
|
||||||
"from":"From Email Address",
|
"from":"From Email Address",
|
||||||
"subject":"Email Subject",
|
"subject":"Email Subject",
|
||||||
"to":"Recipient's address",
|
|
||||||
"cc":"Cc's address",
|
|
||||||
"content":"Email Content",
|
"content":"Email Content",
|
||||||
"equal":"Equal",
|
"equal":"Equal",
|
||||||
"regex":"Regex Match",
|
"regex":"Regex Match",
|
||||||
@ -127,9 +129,9 @@ var zhCN = {
|
|||||||
"sender": "发件人",
|
"sender": "发件人",
|
||||||
"title": "主题",
|
"title": "主题",
|
||||||
"date": "时间",
|
"date": "时间",
|
||||||
"to": "收件人:",
|
"to": "收件人",
|
||||||
"cc": "抄送:",
|
"cc": "抄送",
|
||||||
"sender_desc": "只需要邮箱前缀",
|
"sender_desc": "发件人",
|
||||||
"to_desc": "接收人邮件地址",
|
"to_desc": "接收人邮件地址",
|
||||||
"cc_desc": "抄送人邮箱地址",
|
"cc_desc": "抄送人邮箱地址",
|
||||||
"send": "发送",
|
"send": "发送",
|
||||||
@ -150,6 +152,9 @@ var zhCN = {
|
|||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"security": "安全",
|
"security": "安全",
|
||||||
"SetDomail": "域名设置",
|
"SetDomail": "域名设置",
|
||||||
|
"dns_root_desc": "填入@或者空,不同域名服务商写法不同",
|
||||||
|
"multi_domain_setting": "多域名设置",
|
||||||
|
"multi_domain_setting_desc": "将此邮箱绑定到多个域名上",
|
||||||
"setDNS": "DNS设置",
|
"setDNS": "DNS设置",
|
||||||
"setSSL": "SSL设置",
|
"setSSL": "SSL设置",
|
||||||
"setDatabase": "数据库设置",
|
"setDatabase": "数据库设置",
|
||||||
@ -165,6 +170,7 @@ var zhCN = {
|
|||||||
"type": "类型",
|
"type": "类型",
|
||||||
"db_select_ph": "请选择你的数据库",
|
"db_select_ph": "请选择你的数据库",
|
||||||
"mysql_dsn": "MySQL DSN",
|
"mysql_dsn": "MySQL DSN",
|
||||||
|
"pg_dsn": "PostgreSQL DSN",
|
||||||
"sqlite_db_path": "存储位置",
|
"sqlite_db_path": "存储位置",
|
||||||
"domain_desc": "设置你的域名信息。",
|
"domain_desc": "设置你的域名信息。",
|
||||||
"smtp_domain": "SMTP域名地址",
|
"smtp_domain": "SMTP域名地址",
|
||||||
@ -197,8 +203,6 @@ var zhCN = {
|
|||||||
"rule_do":"执行操作:",
|
"rule_do":"执行操作:",
|
||||||
"from":"发件人地址",
|
"from":"发件人地址",
|
||||||
"subject":"邮件主题",
|
"subject":"邮件主题",
|
||||||
"to":"收件人地址",
|
|
||||||
"cc":"抄送地址",
|
|
||||||
"content":"邮件内容",
|
"content":"邮件内容",
|
||||||
"equal":"等于",
|
"equal":"等于",
|
||||||
"regex":"正则匹配",
|
"regex":"正则匹配",
|
||||||
|
@ -2,7 +2,15 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<el-form label-width="100px" :rules="rules" ref="ruleFormRef" :model="ruleForm" status-icon>
|
<el-form label-width="100px" :rules="rules" ref="ruleFormRef" :model="ruleForm" status-icon>
|
||||||
<el-form-item :label="lang.sender" prop="sender">
|
<el-form-item :label="lang.sender" prop="sender">
|
||||||
<el-input :disabled="!$userInfos.is_admin" v-model="ruleForm.sender" :placeholder="lang.sender_desc"></el-input>
|
|
||||||
|
<div style="display: flex;">
|
||||||
|
<el-input style="max-width: 300px" :disabled="!$userInfos.is_admin" v-model="ruleForm.sender" :placeholder="lang.sender_desc" />
|
||||||
|
<div>@</div>
|
||||||
|
<el-select v-model="ruleForm.pickDomain">
|
||||||
|
<el-option :value="item" v-for="item in ruleForm.domains">{{ item }}</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
@ -84,7 +92,7 @@ import lang from '../i18n/i18n';
|
|||||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||||
import { i18nChangeLanguage } from '@wangeditor/editor'
|
import { i18nChangeLanguage } from '@wangeditor/editor'
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
import useGroupStore from '../stores/group'
|
import useGroupStore from '../stores/group'
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
import { getCurrentInstance } from 'vue'
|
import { getCurrentInstance } from 'vue'
|
||||||
@ -93,9 +101,9 @@ const $http = app.appContext.config.globalProperties.$http
|
|||||||
const $isLogin = app.appContext.config.globalProperties.$isLogin
|
const $isLogin = app.appContext.config.globalProperties.$isLogin
|
||||||
const $userInfos = app.appContext.config.globalProperties.$userInfos
|
const $userInfos = app.appContext.config.globalProperties.$userInfos
|
||||||
|
|
||||||
if (lang.lang == "zhCn"){
|
if (lang.lang == "zhCn") {
|
||||||
i18nChangeLanguage('zh-CN')
|
i18nChangeLanguage('zh-CN')
|
||||||
}else{
|
} else {
|
||||||
i18nChangeLanguage('en')
|
i18nChangeLanguage('en')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,15 +131,20 @@ const ruleForm = reactive({
|
|||||||
receivers: '',
|
receivers: '',
|
||||||
cc: '',
|
cc: '',
|
||||||
subject: '',
|
subject: '',
|
||||||
|
domains:[],
|
||||||
|
pickDomain:""
|
||||||
})
|
})
|
||||||
const fileList = reactive([]);
|
const fileList = reactive([]);
|
||||||
|
|
||||||
|
|
||||||
const init =function(){
|
const init = function () {
|
||||||
if (Object.keys($userInfos.value).length == 0) {
|
if (Object.keys($userInfos.value).length == 0) {
|
||||||
$http.post("/api/user/info", {}).then(res => {
|
$http.post("/api/user/info", {}).then(res => {
|
||||||
if (res.errorNo == 0) {
|
if (res.errorNo == 0) {
|
||||||
$userInfos.value = res.data
|
$userInfos.value = res.data
|
||||||
|
ruleForm.sender = res.data.account
|
||||||
|
ruleForm.domains = res.data.domains
|
||||||
|
ruleForm.pickDomain = res.data.domains[0]
|
||||||
} else {
|
} else {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
@ -139,10 +152,14 @@ const init =function(){
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}else{
|
||||||
|
ruleForm.sender = $userInfos.value.account
|
||||||
|
ruleForm.domains = $userInfos.value.domains
|
||||||
|
ruleForm.pickDomain = $userInfos.value.domains[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
init()
|
init()
|
||||||
ruleForm.sender = $userInfos.value.account
|
|
||||||
|
|
||||||
|
|
||||||
const validateSender = function (rule, value, callback) {
|
const validateSender = function (rule, value, callback) {
|
||||||
@ -240,7 +257,7 @@ const send = function (formEl) {
|
|||||||
let text = editorRef.value.getText()
|
let text = editorRef.value.getText()
|
||||||
|
|
||||||
$http.post("/api/email/send", {
|
$http.post("/api/email/send", {
|
||||||
from: { name: ruleForm.sender, email: "" },
|
from: { name: ruleForm.sender, email: ruleForm.sender + "@" +ruleForm.pickDomain },
|
||||||
to: objectTos,
|
to: objectTos,
|
||||||
cc: objectCcs,
|
cc: objectCcs,
|
||||||
subject: ruleForm.subject,
|
subject: ruleForm.subject,
|
||||||
|
@ -4,13 +4,27 @@
|
|||||||
<el-divider />
|
<el-divider />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span>{{ lang.to }}:
|
<div>{{ lang.to }}:
|
||||||
<span class="userItem" v-for="to in tos">{{ to.Name }} {{ to.EmailAddress }} ;</span>
|
<el-tooltip v-for="to in tos" class="box-item" effect="dark" :content="to.EmailAddress" placement="top">
|
||||||
</span>
|
<el-tag size="small" type="info">{{to.Name != '' ? to.Name : to.EmailAddress }}</el-tag>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span v-if="showCC">{{ lang.cc }}:
|
<div v-if="showCC">{{ lang.cc }}:
|
||||||
<span class="userItem" v-for="ccs in cc">{{ cc.Name }} {{ cc.EmailAddress }} ;</span>
|
<el-tooltip v-for="item in ccs" class="box-item" effect="dark" :content="item.EmailAddress" placement="top">
|
||||||
</span>
|
<el-tag size="small" type="info">{{item.Name != '' ? item.Name : item.EmailAddress }}</el-tag>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>{{ lang.sender }}:
|
||||||
|
<el-tooltip class="box-item" effect="dark" :content="detailData.from_address" placement="top">
|
||||||
|
<el-tag size="small" type="info">{{detailData.from_name != '' ? detailData.from_name : detailData.from_address }}</el-tag>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>{{ lang.date }}:
|
||||||
|
{{ detailData.send_date }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-divider />
|
<el-divider />
|
||||||
<div class="content" id="text" v-if="detailData.html == ''">
|
<div class="content" id="text" v-if="detailData.html == ''">
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item @click="move(group.id)" v-for="group in groupList">{{ group.name
|
<el-dropdown-item @click="move(group.id)" v-for="group in groupList">{{ group.name
|
||||||
}}</el-dropdown-item>
|
}}</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@ -26,43 +26,43 @@
|
|||||||
<el-table ref="taskTableDataRef" @selection-change="selectionLineChange" :data="data" :show-header="true"
|
<el-table ref="taskTableDataRef" @selection-change="selectionLineChange" :data="data" :show-header="true"
|
||||||
:border="false" @row-click="rowClick" :row-style="rowStyle">
|
:border="false" @row-click="rowClick" :row-style="rowStyle">
|
||||||
<el-table-column type="selection" width="30" />
|
<el-table-column type="selection" width="30" />
|
||||||
<el-table-column prop="title" label="" width="50">
|
<el-table-column prop="is_read" label="" width="50">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div>
|
<div>
|
||||||
<span v-if="!scope.row.is_read">
|
<span v-if="!scope.row.is_read">
|
||||||
{{ lang.new }}
|
{{ lang.new }}
|
||||||
</span>
|
</span>
|
||||||
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.dangerous">
|
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.dangerous">
|
||||||
<el-tooltip effect="dark"
|
<el-tooltip effect="dark" :content="lang.dangerous" placement="top-start">
|
||||||
:content="lang.dangerous"
|
|
||||||
placement="top-start">
|
|
||||||
!
|
!
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.error != ''">
|
<span style="font-weight: 900;color: #FF0000;" v-if="scope.row.error != ''">
|
||||||
<el-tooltip effect="dark"
|
<el-tooltip effect="dark" :content="scope.row.error" placement="top-start">
|
||||||
:content="scope.row.error"
|
|
||||||
placement="top-start">
|
|
||||||
!
|
!
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="title" :label="lang.sender" width="150">
|
<el-table-column prop="title" :label="lang.sender" width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-if="scope.row.is_read">
|
<el-tooltip class="box-item" effect="dark" :content="scope.row.sender.EmailAddress" placement="top">
|
||||||
<div v-if="scope.row.sender.Name != ''">{{ scope.row.sender.Name }}</div>
|
<el-tag size="small" type="info">{{scope.row.sender.Name != '' ? scope.row.sender.Name : scope.row.sender.EmailAddress }}</el-tag>
|
||||||
{{ scope.row.sender.EmailAddress }}
|
</el-tooltip>
|
||||||
</span>
|
|
||||||
<span v-else style="font-weight:bolder;">
|
|
||||||
<div v-if="scope.row.sender.Name != ''">{{ scope.row.sender.Name }}</div>
|
|
||||||
{{ scope.row.sender.EmailAddress }}
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="title" :label="lang.to" width="150">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip v-for="toInfo in scope.row.to" class="box-item" effect="dark" :content="toInfo.EmailAddress" placement="top">
|
||||||
|
<el-tag size="small" type="info">{{toInfo.Name != '' ? toInfo.Name : toInfo.EmailAddress }}</el-tag>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column prop="desc" :label="lang.title">
|
<el-table-column prop="desc" :label="lang.title">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div v-if="scope.row.is_read">{{ scope.row.title }}</div>
|
<div v-if="scope.row.is_read">{{ scope.row.title }}</div>
|
||||||
@ -103,7 +103,7 @@ const app = getCurrentInstance()
|
|||||||
const $http = app.appContext.config.globalProperties.$http
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const groupStore = useGroupStore()
|
const groupStore = useGroupStore()
|
||||||
@ -222,6 +222,9 @@ const del = function () {
|
|||||||
ids.push(element.id)
|
ids.push(element.id)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let groupTag = JSON.parse(tag)
|
||||||
|
|
||||||
|
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
lang.del_email_confirm,
|
lang.del_email_confirm,
|
||||||
'Warning',
|
'Warning',
|
||||||
@ -232,7 +235,7 @@ const del = function () {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
$http.post("/api/email/del", { "ids": ids }).then(res => {
|
$http.post("/api/email/del", { "ids": ids ,"forcedDel":groupTag.status == 3 }).then(res => {
|
||||||
if (res.errorNo == 0) {
|
if (res.errorNo == 0) {
|
||||||
updateList()
|
updateList()
|
||||||
ElMessage({
|
ElMessage({
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
@change="dbSettings.dsn = ''">
|
@change="dbSettings.dsn = ''">
|
||||||
<el-option label="MySQL" value="mysql" />
|
<el-option label="MySQL" value="mysql" />
|
||||||
<el-option label="SQLite3" value="sqlite" />
|
<el-option label="SQLite3" value="sqlite" />
|
||||||
|
<el-option label="PostgreSQL" value="postgres" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@ -40,6 +41,11 @@
|
|||||||
placeholder="root:12345@tcp(127.0.0.1:3306)/pmail?parseTime=True&loc=Local"></el-input>
|
placeholder="root:12345@tcp(127.0.0.1:3306)/pmail?parseTime=True&loc=Local"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="lang.pg_dsn" v-if="dbSettings.type == 'postgres'">
|
||||||
|
<el-input :rows="2" type="textarea" v-model="dbSettings.dsn"
|
||||||
|
placeholder="postgres://postgres:12345@127.0.0.1:5432/pmail?sslmode=disable"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item :label="lang.sqlite_db_path" v-if="dbSettings.type == 'sqlite'">
|
<el-form-item :label="lang.sqlite_db_path" v-if="dbSettings.type == 'sqlite'">
|
||||||
<el-input v-model="dbSettings.dsn" placeholder="./config/pmail.db"></el-input>
|
<el-input v-model="dbSettings.dsn" placeholder="./config/pmail.db"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -92,6 +98,15 @@
|
|||||||
<el-form-item :label="lang.web_domain">
|
<el-form-item :label="lang.web_domain">
|
||||||
<el-input placeholder="pmail.domain.com" v-model="domainSettings.web_domain"></el-input>
|
<el-input placeholder="pmail.domain.com" v-model="domainSettings.web_domain"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item :label="lang.multi_domain_setting">
|
||||||
|
<span>{{ lang.multi_domain_setting_desc }} <el-button @click="addDomain" size="small"
|
||||||
|
type="success" :icon="Plus" circle></el-button></span>
|
||||||
|
<el-input :placeholder="'domain' + i + '.com'" v-for="(item, i) in domainSettings.multi_domain"
|
||||||
|
v-model="domainSettings.multi_domain[i]"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -102,9 +117,19 @@
|
|||||||
<h2>{{ lang.setDNS }}</h2>
|
<h2>{{ lang.setDNS }}</h2>
|
||||||
<div style="margin-top: 10px;">{{ lang.dns_desc }}</div>
|
<div style="margin-top: 10px;">{{ lang.dns_desc }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form" width="600px">
|
<div class="form" width="600px" v-for="(info,domain) in dnsInfos">
|
||||||
<el-table :data="dnsInfos" border style="width: 100%">
|
<h3>{{ domain }}</h3>
|
||||||
<el-table-column prop="host" label="HOSTNAME" width="110px" />
|
<el-table :data="info" border style="width: 100%">
|
||||||
|
<el-table-column prop="host" label="HOSTNAME" width="110px" >
|
||||||
|
<template #default="scope">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
<el-tooltip :content="lang.dns_root_desc" placement="top" v-if="scope.row.host == '' || scope.row.host == '@' ">
|
||||||
|
{{ scope.row.host }}
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else>{{ scope.row.host }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="type" label="TYPE" width="110px" />
|
<el-table-column prop="type" label="TYPE" width="110px" />
|
||||||
<el-table-column prop="value" label="VALUE">
|
<el-table-column prop="value" label="VALUE">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@ -115,7 +140,6 @@
|
|||||||
<span v-else>{{ scope.row.value }}</span>
|
<span v-else>{{ scope.row.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="ttl" label="TTL" width="110px" />
|
<el-table-column prop="ttl" label="TTL" width="110px" />
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -205,6 +229,11 @@ import { ElMessage } from 'element-plus'
|
|||||||
import lang from '../i18n/i18n';
|
import lang from '../i18n/i18n';
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { getCurrentInstance } from 'vue'
|
import { getCurrentInstance } from 'vue'
|
||||||
|
import {
|
||||||
|
Plus
|
||||||
|
} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
|
||||||
const app = getCurrentInstance()
|
const app = getCurrentInstance()
|
||||||
const $http = app.appContext.config.globalProperties.$http
|
const $http = app.appContext.config.globalProperties.$http
|
||||||
const waitDesc = ref(lang.wait_desc)
|
const waitDesc = ref(lang.wait_desc)
|
||||||
@ -225,7 +254,8 @@ const dbSettings = reactive({
|
|||||||
|
|
||||||
const domainSettings = reactive({
|
const domainSettings = reactive({
|
||||||
"web_domain": "",
|
"web_domain": "",
|
||||||
"smtp_domain": ""
|
"smtp_domain": "",
|
||||||
|
"multi_domain": []
|
||||||
})
|
})
|
||||||
|
|
||||||
const sslSettings = reactive({
|
const sslSettings = reactive({
|
||||||
@ -241,11 +271,15 @@ const active = ref(0)
|
|||||||
const fullscreenLoading = ref(false)
|
const fullscreenLoading = ref(false)
|
||||||
const dnsChecking = ref(false)
|
const dnsChecking = ref(false)
|
||||||
|
|
||||||
const dnsInfos = ref([
|
const dnsInfos = ref({})
|
||||||
])
|
|
||||||
|
|
||||||
const port = ref(80)
|
const port = ref(80)
|
||||||
|
|
||||||
|
|
||||||
|
const addDomain = () => {
|
||||||
|
domainSettings.multi_domain.push([])
|
||||||
|
}
|
||||||
|
|
||||||
const setPassword = () => {
|
const setPassword = () => {
|
||||||
if (adminSettings.hadSeted) {
|
if (adminSettings.hadSeted) {
|
||||||
active.value++;
|
active.value++;
|
||||||
@ -302,6 +336,7 @@ const getDomainConfig = () => {
|
|||||||
} else {
|
} else {
|
||||||
domainSettings.web_domain = res.data.web_domain;
|
domainSettings.web_domain = res.data.web_domain;
|
||||||
domainSettings.smtp_domain = res.data.smtp_domain;
|
domainSettings.smtp_domain = res.data.smtp_domain;
|
||||||
|
domainSettings.multi_domain = res.data.domains;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -385,7 +420,11 @@ const checkStatus = () => {
|
|||||||
checkStatus()
|
checkStatus()
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} 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) => {
|
}).catch((error) => {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@ -396,7 +435,13 @@ const checkStatus = () => {
|
|||||||
|
|
||||||
|
|
||||||
const setDomainConfig = () => {
|
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) {
|
if (res.errorNo != 0) {
|
||||||
ElMessage.error(res.errorMsg)
|
ElMessage.error(res.errorMsg)
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,13 +13,12 @@ import (
|
|||||||
var IsInit bool
|
var IsInit bool
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
LogLevel string `json:"logLevel"` // 日志级别
|
LogLevel string `json:"logLevel"` // 日志级别
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Domains []string `json:"domains"` //多域名设置,把所有收信域名都填进去
|
Domains []string `json:"domains"` //多域名设置,把所有收信域名都填进去
|
||||||
WebDomain string `json:"webDomain"`
|
WebDomain string `json:"webDomain"`
|
||||||
DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
|
DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"`
|
||||||
SSLType string `json:"sslType"` // 0表示自动生成证书,HTTP挑战模式,1表示用户上传证书,2表示自动-DNS挑战模式
|
SSLType string `json:"sslType"` // 0表示自动生成证书,HTTP挑战模式,1表示用户上传证书,2表示自动-DNS挑战模式
|
||||||
//DomainServiceName string `json:"domainServerName"` // 域名服务商名称
|
|
||||||
SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
|
SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"`
|
||||||
SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
|
SSLPublicKeyPath string `json:"SSLPublicKeyPath"`
|
||||||
DbDSN string `json:"dbDSN"`
|
DbDSN string `json:"dbDSN"`
|
||||||
@ -52,11 +51,12 @@ func (c *Config) SetSetupPort(setupPort int) {
|
|||||||
|
|
||||||
const DBTypeMySQL = "mysql"
|
const DBTypeMySQL = "mysql"
|
||||||
const DBTypeSQLite = "sqlite"
|
const DBTypeSQLite = "sqlite"
|
||||||
|
const DBTypePostgres = "postgres"
|
||||||
const SSLTypeAutoHTTP = "0" //自动生成证书
|
const SSLTypeAutoHTTP = "0" //自动生成证书
|
||||||
const SSLTypeAutoDNS = "2" //自动生成证书,DNS api验证
|
const SSLTypeAutoDNS = "2" //自动生成证书,DNS api验证
|
||||||
const SSLTypeUser = "1" //用户上传证书
|
const SSLTypeUser = "1" //用户上传证书
|
||||||
|
|
||||||
var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite}
|
var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite, DBTypePostgres}
|
||||||
|
|
||||||
var Instance *Config = &Config{}
|
var Instance *Config = &Config{}
|
||||||
|
|
||||||
|
@ -11,7 +11,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type emailDeleteRequest struct {
|
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) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = del_email.DelEmail(ctx, reqData.IDs)
|
err = del_email.DelEmail(ctx, reqData.IDs, reqData.ForcedDel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
|
@ -26,6 +26,7 @@ type emilItem struct {
|
|||||||
Datetime string `json:"datetime"`
|
Datetime string `json:"datetime"`
|
||||||
IsRead bool `json:"is_read"`
|
IsRead bool `json:"is_read"`
|
||||||
Sender User `json:"sender"`
|
Sender User `json:"sender"`
|
||||||
|
To []User `json:"to"`
|
||||||
Dangerous bool `json:"dangerous"`
|
Dangerous bool `json:"dangerous"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
@ -76,6 +77,9 @@ func EmailList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
var sender User
|
var sender User
|
||||||
_ = json.Unmarshal([]byte(email.Sender), &sender)
|
_ = json.Unmarshal([]byte(email.Sender), &sender)
|
||||||
|
|
||||||
|
var tos []User
|
||||||
|
_ = json.Unmarshal([]byte(email.To), &tos)
|
||||||
|
|
||||||
lst = append(lst, &emilItem{
|
lst = append(lst, &emilItem{
|
||||||
ID: email.Id,
|
ID: email.Id,
|
||||||
Title: email.Subject,
|
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"),
|
Datetime: email.SendDate.Format("2006-01-02 15:04:05"),
|
||||||
IsRead: email.IsRead == 1,
|
IsRead: email.IsRead == 1,
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
|
To: tos,
|
||||||
Dangerous: email.SPFCheck == 0 && email.DKIMCheck == 0,
|
Dangerous: email.SPFCheck == 0 && email.DKIMCheck == 0,
|
||||||
Error: email.Error.String,
|
Error: email.Error.String,
|
||||||
})
|
})
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"pmail/hooks/framework"
|
"pmail/hooks/framework"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
|
"pmail/utils/array"
|
||||||
"pmail/utils/async"
|
"pmail/utils/async"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"pmail/utils/send"
|
"pmail/utils/send"
|
||||||
@ -69,6 +70,14 @@ func Send(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
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 != "" {
|
if reqData.From.Email == "" && reqData.From.Name != "" {
|
||||||
reqData.From.Email = reqData.From.Name + "@" + config.Instance.Domain
|
reqData.From.Email = reqData.From.Name + "@" + config.Instance.Domain
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"pmail/dto"
|
"pmail/dto"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
|
"pmail/models"
|
||||||
"pmail/services/group"
|
"pmail/services/group"
|
||||||
"pmail/utils/array"
|
"pmail/utils/array"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
@ -66,17 +67,19 @@ func AddGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
log.WithContext(ctx).Errorf("%+v", err)
|
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)
|
var newGroup models.Group = models.Group{
|
||||||
|
Name: reqData.Name,
|
||||||
|
ParentId: reqData.ParentId,
|
||||||
|
UserId: ctx.UserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Instance.Insert(&newGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
|
response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
id, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
response.NewSuccessResponse(newGroup.ID).FPrint(w)
|
||||||
response.NewErrorResponse(response.ServerError, "DBError", err.Error()).FPrint(w)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
response.NewSuccessResponse(id).FPrint(w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type delGroupRequest struct {
|
type delGroupRequest struct {
|
||||||
|
@ -6,11 +6,13 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"pmail/config"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/i18n"
|
"pmail/i18n"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/session"
|
"pmail/session"
|
||||||
|
"pmail/utils/array"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"pmail/utils/errors"
|
"pmail/utils/errors"
|
||||||
"pmail/utils/password"
|
"pmail/utils/password"
|
||||||
@ -44,10 +46,16 @@ func Login(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
if user.ID != 0 {
|
if user.ID != 0 {
|
||||||
userStr, _ := json.Marshal(user)
|
userStr, _ := json.Marshal(user)
|
||||||
session.Instance.Put(req.Context(), "user", string(userStr))
|
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{
|
response.NewSuccessResponse(map[string]any{
|
||||||
"account": user.Account,
|
"account": user.Account,
|
||||||
"name": user.Name,
|
"name": user.Name,
|
||||||
"is_admin": user.IsAdmin,
|
"is_admin": user.IsAdmin,
|
||||||
|
"domains": domains,
|
||||||
}).FPrint(w)
|
}).FPrint(w)
|
||||||
} else {
|
} else {
|
||||||
response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "aperror"), "").FPrint(w)
|
response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "aperror"), "").FPrint(w)
|
||||||
|
@ -3,6 +3,7 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cast"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -41,9 +42,8 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
var reqData map[string]string
|
var reqData map[string]string
|
||||||
err = json.Unmarshal(reqBytes, &reqData)
|
err = json.Unmarshal(reqBytes, &reqData)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewSuccessResponse("").FPrint(w)
|
response.NewErrorResponse(response.ServerError, "", err.Error()).FPrint(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reqData["step"] == "database" && reqData["action"] == "set" {
|
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 {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
@ -83,7 +83,7 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reqData["step"] == "password" && reqData["action"] == "set" {
|
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 {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
@ -93,20 +93,21 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reqData["step"] == "domain" && reqData["action"] == "get" {
|
if reqData["step"] == "domain" && reqData["action"] == "get" {
|
||||||
smtpDomain, webDomain, err := setup.GetDomainSettings()
|
smtpDomain, webDomain, domains, err := setup.GetDomainSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response.NewSuccessResponse(map[string]string{
|
response.NewSuccessResponse(map[string]any{
|
||||||
"smtp_domain": smtpDomain,
|
"smtp_domain": smtpDomain,
|
||||||
"web_domain": webDomain,
|
"web_domain": webDomain,
|
||||||
|
"domains": domains,
|
||||||
}).FPrint(w)
|
}).FPrint(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if reqData["step"] == "domain" && reqData["action"] == "set" {
|
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 {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
@ -148,20 +149,20 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
|||||||
keyPath := reqData["key_path"]
|
keyPath := reqData["key_path"]
|
||||||
crtPath := reqData["crt_path"]
|
crtPath := reqData["crt_path"]
|
||||||
|
|
||||||
_, err := os.Stat(keyPath)
|
_, err := os.Stat(cast.ToString(keyPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = os.Stat(crtPath)
|
_, err = os.Stat(cast.ToString(crtPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
|
||||||
return
|
return
|
||||||
|
@ -7,9 +7,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"pmail/config"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
"pmail/dto/response"
|
"pmail/dto/response"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
|
"pmail/utils/array"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
"pmail/utils/password"
|
"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) {
|
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{
|
response.NewSuccessResponse(map[string]any{
|
||||||
"account": ctx.UserAccount,
|
"account": ctx.UserAccount,
|
||||||
"name": ctx.UserName,
|
"name": ctx.UserName,
|
||||||
"is_admin": ctx.IsAdmin,
|
"is_admin": ctx.IsAdmin,
|
||||||
|
"domains": domains,
|
||||||
}).FPrint(w)
|
}).FPrint(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
@ -27,6 +28,10 @@ func Init(version string) error {
|
|||||||
Instance, err = xorm.NewEngine("sqlite", dsn)
|
Instance, err = xorm.NewEngine("sqlite", dsn)
|
||||||
Instance.SetMaxOpenConns(1)
|
Instance.SetMaxOpenConns(1)
|
||||||
Instance.SetMaxIdleConns(1)
|
Instance.SetMaxIdleConns(1)
|
||||||
|
case "postgres":
|
||||||
|
Instance, err = xorm.NewEngine("postgres", dsn)
|
||||||
|
Instance.SetMaxOpenConns(100)
|
||||||
|
Instance.SetMaxIdleConns(10)
|
||||||
default:
|
default:
|
||||||
return errors.New("Database Type Error!")
|
return errors.New("Database Type Error!")
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,22 @@ go 1.22.0
|
|||||||
require (
|
require (
|
||||||
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea
|
github.com/Jinnrry/gopop v0.0.0-20231113115125-fbdf52ae39ea
|
||||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885
|
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/sqlite3store v0.0.0-20240316134038-7e11d57e8885
|
||||||
github.com/alexedwards/scs/v2 v2.8.0
|
github.com/alexedwards/scs/v2 v2.8.0
|
||||||
github.com/emersion/go-message v0.18.1
|
github.com/emersion/go-message v0.18.1
|
||||||
github.com/emersion/go-msgauth v0.6.8
|
github.com/emersion/go-msgauth v0.6.8
|
||||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
|
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
|
||||||
github.com/emersion/go-smtp v0.21.2
|
github.com/emersion/go-smtp v0.21.3
|
||||||
github.com/go-acme/lego/v4 v4.17.3
|
github.com/go-acme/lego/v4 v4.17.4
|
||||||
github.com/go-sql-driver/mysql v1.8.1
|
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/mileusna/spf v0.9.5
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cast v1.6.0
|
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
|
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/builder v0.3.13
|
||||||
xorm.io/xorm v1.3.9
|
xorm.io/xorm v1.3.9
|
||||||
)
|
)
|
||||||
@ -27,14 +29,14 @@ require (
|
|||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // 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/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
golang.org/x/mod v0.18.0 // indirect
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
golang.org/x/net v0.26.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/tools v0.22.0 // indirect
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // 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/mathutil v1.6.0 // indirect
|
||||||
modernc.org/memory v1.8.0 // indirect
|
modernc.org/memory v1.8.0 // indirect
|
||||||
modernc.org/strutil v1.2.0 // indirect
|
modernc.org/strutil v1.2.0 // indirect
|
||||||
|
@ -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/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 h1:C7QAamNjR5yz6di4KJWAKcnxueKBgq4L/JGXhlnu35w=
|
||||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885/go.mod h1:p8jK3D80sw1PFrCSdlcJF1O75bp55HqbgDyyCLM0FrE=
|
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 h1:+DCxWg/ojncqS+TGAuRUoV7OfG/S4doh0pcpAwEcow0=
|
||||||
github.com/alexedwards/scs/sqlite3store v0.0.0-20240316134038-7e11d57e8885/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss=
|
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=
|
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-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 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.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 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
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=
|
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/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 h1:5our7Qdyik0abag40abdmQuytq97iweaNHFMT4pYDnQ=
|
||||||
github.com/go-acme/lego/v4 v4.17.3/go.mod h1:Ol6l04hnmavqVHKYS/ByhXXqE64x8yVYhomha82uAUk=
|
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 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.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.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 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
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/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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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=
|
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.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 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
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 h1:P6cmaIBwrhZaP9stXMzGOtxe+gIu65OVbZCmrAv9rgU=
|
||||||
github.com/mileusna/spf v0.9.5/go.mod h1:o6IdTae6QptAbLgx/+ueXSTSpkG+f1cqLemQJSew8sI=
|
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=
|
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.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 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
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.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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-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.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 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
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.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/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.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 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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=
|
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 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
|
||||||
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
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 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo=
|
||||||
modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM=
|
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 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
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/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 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M=
|
||||||
modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ=
|
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 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
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/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 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM=
|
||||||
modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw=
|
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 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
@ -243,6 +243,7 @@ func testCreateUser(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if data.ErrorNo != 0 {
|
if data.ErrorNo != 0 {
|
||||||
|
t.Error(data)
|
||||||
t.Error("Create User Api Error!")
|
t.Error("Create User Api Error!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,6 +312,10 @@ func testDataBaseSet(t *testing.T) {
|
|||||||
if array.InArray("mysql", argList) {
|
if array.InArray("mysql", argList) {
|
||||||
configData = `
|
configData = `
|
||||||
{"action":"set","step":"database","db_type":"mysql","db_dsn":"root:githubTest@tcp(mysql:3306)/pmail?parseTime=True"}
|
{"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"}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,7 +312,7 @@ func (a action) Noop(session *gopop.Session) error {
|
|||||||
func (a action) Quit(session *gopop.Session) error {
|
func (a action) Quit(session *gopop.Session) error {
|
||||||
log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
|
log.WithContext(session.Ctx).Debugf("POP3 CMD: QUIT ")
|
||||||
if len(session.DeleteIds) > 0 {
|
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
|
return nil
|
||||||
|
@ -1,50 +1,55 @@
|
|||||||
package del_email
|
package del_email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/spf13/cast"
|
||||||
"pmail/consts"
|
"pmail/consts"
|
||||||
"pmail/db"
|
"pmail/db"
|
||||||
"pmail/models"
|
"pmail/models"
|
||||||
"pmail/utils/context"
|
"pmail/utils/context"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
import . "xorm.io/builder"
|
|
||||||
|
|
||||||
func DelEmail(ctx *context.Context, ids []int) error {
|
func DelEmail(ctx *context.Context, ids []int64, forcedDel bool) error {
|
||||||
|
session := db.Instance.NewSession()
|
||||||
if len(ids) == 0 {
|
defer session.Close()
|
||||||
return nil
|
if err := session.Begin(); err != 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)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for _, id := range ids {
|
||||||
|
err := deleteOne(ctx, session, cast.ToInt64(id), forcedDel)
|
||||||
|
if err != nil {
|
||||||
|
session.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return session.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
_, err = db.Instance.Table(&models.UserEmail{}).Where(where, params...).Update(map[string]interface{}{"status": consts.EmailStatusDel})
|
type num struct {
|
||||||
|
Num int `xorm:"num"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
// 先删除关联关系
|
||||||
|
var ue models.UserEmail
|
||||||
|
_, err := session.Table(&models.UserEmail{}).Where("email_id=? and user_id=?", id, ctx.UserID).Delete(&ue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("del email err: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
return err
|
// 检查email是否还有人有权限
|
||||||
}
|
var Num num
|
||||||
|
_, err = session.Table(&models.UserEmail{}).Select("count(1) as num").Where("email_id=? ", id).Get(&Num)
|
||||||
func DelEmailI64(ctx *context.Context, ids []int64) error {
|
if err != nil {
|
||||||
|
return err
|
||||||
if len(ids) == 0 {
|
}
|
||||||
return nil
|
if Num.Num == 0 {
|
||||||
}
|
var email models.Email
|
||||||
|
_, err = session.Table(&email).Where("id=?", id).Delete(&email)
|
||||||
where, params, err := ToSQL(Eq{"user_id": ctx.UserID}.And(Eq{"email_id": ids}))
|
|
||||||
|
|
||||||
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})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("del email err: %v", err)
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ func genSQL(ctx *context.Context, count bool, tagInfo dto.SearchTag, keyword str
|
|||||||
sql += " order by e.id desc"
|
sql += " order by e.id desc"
|
||||||
|
|
||||||
if limit < 10000 {
|
if limit < 10000 {
|
||||||
sql += fmt.Sprintf(" limit %d,%d ", offset, limit)
|
sql += fmt.Sprintf(" LIMIT %d OFFSET %d ", limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sql, sqlParams
|
return sql, sqlParams
|
||||||
|
@ -1,54 +1 @@
|
|||||||
package list
|
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -43,7 +43,14 @@ func GetAdminPassword(ctx *context.Context) (string, error) {
|
|||||||
|
|
||||||
func SetAdminPassword(ctx *context.Context, account, pwd string) error {
|
func SetAdminPassword(ctx *context.Context, account, pwd string) error {
|
||||||
encodePwd := password.Encode(pwd)
|
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 {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
}
|
}
|
||||||
|
@ -17,19 +17,24 @@ type DNSItem struct {
|
|||||||
Tips string `json:"tips"`
|
Tips string `json:"tips"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDNSSettings(ctx *context.Context) ([]*DNSItem, error) {
|
func GetDNSSettings(ctx *context.Context) (map[string][]*DNSItem, error) {
|
||||||
configData, err := ReadConfig()
|
configData, err := ReadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err)
|
return nil, errors.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := []*DNSItem{
|
ret := make(map[string][]*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")},
|
for _, domain := range configData.Domains {
|
||||||
{Type: "A", Host: "", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
ret[domain] = []*DNSItem{
|
||||||
{Type: "MX", Host: "", Value: fmt.Sprintf("smtp.%s", configData.Domain), TTL: 3600},
|
{Type: "A", Host: "smtp", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
||||||
{Type: "TXT", Host: "", Value: "v=spf1 a mx ~all", TTL: 3600},
|
{Type: "A", Host: "pop", Value: ip.GetIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")},
|
||||||
{Type: "TXT", Host: "default._domainkey", Value: auth.DkimGen(), TTL: 3600},
|
{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
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"pmail/utils/array"
|
||||||
"pmail/utils/errors"
|
"pmail/utils/errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDomainSettings() (string, string, error) {
|
func GetDomainSettings() (string, string, []string, error) {
|
||||||
configData, err := ReadConfig()
|
configData, err := ReadConfig()
|
||||||
if err != nil {
|
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()
|
configData, err := ReadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err)
|
return errors.Wrap(err)
|
||||||
@ -27,6 +29,17 @@ func SetDomainSettings(smtpDomain, webDomain string) error {
|
|||||||
return errors.New("web domain must not empty!")
|
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.Domain = smtpDomain
|
||||||
configData.WebDomain = webDomain
|
configData.WebDomain = webDomain
|
||||||
|
|
||||||
|
@ -102,8 +102,14 @@ func renewCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config) error {
|
|||||||
|
|
||||||
myUser.Registration = reg
|
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{
|
request := certificate.ObtainRequest{
|
||||||
Domains: []string{"smtp." + cfg.Domain, cfg.WebDomain, "pop." + cfg.Domain},
|
Domains: domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,8 +181,14 @@ func generateCertificate(privateKey *ecdsa.PrivateKey, cfg *config.Config, newAc
|
|||||||
|
|
||||||
myUser.Registration = reg
|
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{
|
request := certificate.ObtainRequest{
|
||||||
Domains: []string{"smtp." + cfg.Domain, cfg.WebDomain, "pop." + cfg.Domain},
|
Domains: domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package session
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alexedwards/scs/mysqlstore"
|
"github.com/alexedwards/scs/mysqlstore"
|
||||||
|
"github.com/alexedwards/scs/postgresstore"
|
||||||
"github.com/alexedwards/scs/sqlite3store"
|
"github.com/alexedwards/scs/sqlite3store"
|
||||||
"github.com/alexedwards/scs/v2"
|
"github.com/alexedwards/scs/v2"
|
||||||
"pmail/config"
|
"pmail/config"
|
||||||
@ -17,9 +18,16 @@ func Init() {
|
|||||||
Instance.Lifetime = 7 * 24 * time.Hour
|
Instance.Lifetime = 7 * 24 * time.Hour
|
||||||
// 使用db存储session数据,目前为了架构简单,
|
// 使用db存储session数据,目前为了架构简单,
|
||||||
// 暂不引入redis存储,如果日后性能存在瓶颈,可以将session迁移到redis
|
// 暂不引入redis存储,如果日后性能存在瓶颈,可以将session迁移到redis
|
||||||
if config.Instance.DbType == "mysql" {
|
|
||||||
|
switch config.Instance.DbType {
|
||||||
|
case config.DBTypeMySQL:
|
||||||
Instance.Store = mysqlstore.New(db.Instance.DB().DB)
|
Instance.Store = mysqlstore.New(db.Instance.DB().DB)
|
||||||
} else {
|
case config.DBTypeSQLite:
|
||||||
Instance.Store = sqlite3store.New(db.Instance.DB().DB)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ type mxDomain struct {
|
|||||||
|
|
||||||
// Forward 转发邮件
|
// Forward 转发邮件
|
||||||
func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) error {
|
func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) error {
|
||||||
|
_, fromDomain := e.From.GetDomainAccount()
|
||||||
|
|
||||||
log.WithContext(ctx).Debugf("开始转发邮件")
|
log.WithContext(ctx).Debugf("开始转发邮件")
|
||||||
b := e.ForwardBuildBytes(ctx, forwardAddress)
|
b := e.ForwardBuildBytes(ctx, forwardAddress)
|
||||||
@ -75,16 +76,16 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
|
|||||||
domain := domain
|
domain := domain
|
||||||
tos := tos
|
tos := tos
|
||||||
as.WaitProcess(func(p any) {
|
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 {
|
if err != nil {
|
||||||
// EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
|
// EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
|
||||||
if errors.Is(err, smtp.NoSupportSTARTTLSError) || err.Error() == "EOF" {
|
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 {
|
if err != nil {
|
||||||
log.WithContext(ctx).Warnf("Unsafe! %s Server Not Support SMTPS & STARTTLS", domain.domain)
|
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 certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
|
||||||
// 单测使用
|
// 单测使用
|
||||||
if domain.domain == "localhost" {
|
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 {
|
} else if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
|
||||||
if hostnameErr.Certificate != nil {
|
if hostnameErr.Certificate != nil {
|
||||||
certificateHostName := hostnameErr.Certificate.DNSNames
|
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) {
|
func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
|
||||||
|
|
||||||
|
_, fromDomain := e.From.GetDomainAccount()
|
||||||
|
|
||||||
b := e.BuildBytes(ctx, true)
|
b := e.BuildBytes(ctx, true)
|
||||||
|
|
||||||
log.WithContext(ctx).Debugf("Message Infos : %s", string(b))
|
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
|
tos := tos
|
||||||
as.WaitProcess(func(p any) {
|
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 {
|
if err != nil {
|
||||||
// EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
|
// EOF 表示未知错误,此时降级为非tls连接发送(目前仅139邮箱有这个问题)
|
||||||
if errors.Is(err, smtp.NoSupportSTARTTLSError) || err.Error() == "EOF" {
|
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 {
|
if err != nil {
|
||||||
log.WithContext(ctx).Warnf("Unsafe! %s Server Not Support SMTPS & STARTTLS", domain.domain)
|
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 certificateErr, ok := err.(*tls.CertificateVerificationError); ok {
|
||||||
// 单测使用
|
// 单测使用
|
||||||
if domain.domain == "localhost" {
|
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 {
|
} else if hostnameErr, is := certificateErr.Err.(x509.HostnameError); is {
|
||||||
if hostnameErr.Certificate != nil {
|
if hostnameErr.Certificate != nil {
|
||||||
certificateHostName := hostnameErr.Certificate.DNSNames
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
//
|
//
|
||||||
// https://godoc.org/?q=smtp
|
// https://godoc.org/?q=smtp
|
||||||
//
|
//
|
||||||
// 在go原始SMTP协议的基础上修复了TLS验证错误、支持了SMTPS协议
|
// 在go原始SMTP协议的基础上修复了TLS验证错误、支持了SMTPS协议、 支持自定义HELLO命令的域名信息
|
||||||
package smtp
|
package smtp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -58,17 +58,17 @@ type Client struct {
|
|||||||
|
|
||||||
// Dial returns a new Client connected to an SMTP server at addr.
|
// Dial returns a new Client connected to an SMTP server at addr.
|
||||||
// The addr must include a port, as in "mail.example.com:smtp".
|
// 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)
|
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
host, _, _ := net.SplitHostPort(addr)
|
host, _, _ := net.SplitHostPort(addr)
|
||||||
return NewClient(conn, host)
|
return NewClient(conn, host, fromDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// with tls
|
// with tls
|
||||||
func DialTls(addr, domain string) (*Client, error) {
|
func DialTls(addr, domain, fromDomain string) (*Client, error) {
|
||||||
// TLS config
|
// TLS config
|
||||||
tlsconfig := &tls.Config{
|
tlsconfig := &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
@ -80,20 +80,24 @@ func DialTls(addr, domain string) (*Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
host, _, _ := net.SplitHostPort(addr)
|
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
|
// NewClient returns a new Client using an existing connection and host as a
|
||||||
// server name to be used when authenticating.
|
// 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)
|
text := textproto.NewConn(conn)
|
||||||
_, _, err := text.ReadResponse(220)
|
_, _, err := text.ReadResponse(220)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
text.Close()
|
text.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
localName := "domain.com"
|
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
|
localName = config.Instance.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +337,7 @@ func (c *Client) Data() (io.WriteCloser, error) {
|
|||||||
return &dataCloser{c, c.Text.DotWriter()}, nil
|
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 {
|
if err := validateLine(from); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -342,7 +346,7 @@ func SendMailWithTls(domain string, addr string, a smtp.Auth, from string, to []
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c, err := DialTls(addr, domain)
|
c, err := DialTls(addr, domain, fromDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// functionality. Higher-level packages exist outside of the standard
|
||||||
// library.
|
// library.
|
||||||
// 修复TSL验证问题
|
// 修复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 {
|
if err := validateLine(from); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -411,7 +415,7 @@ func SendMail(domain string, addr string, a smtp.Auth, from string, to []string,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c, err := Dial(addr)
|
c, err := Dial(addr, fromDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -465,7 +469,7 @@ func SendMail(domain string, addr string, a smtp.Auth, from string, to []string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendMailUnsafe 无TLS加密的邮件发送方式
|
// 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 {
|
if err := validateLine(from); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -474,7 +478,7 @@ func SendMailUnsafe(domain string, addr string, a smtp.Auth, from string, to []s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c, err := Dial(addr)
|
c, err := Dial(addr, fromDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user