diff --git a/.gitignore b/.gitignore index 7b19d306..7f94dad3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea .DS_Store dist -output \ No newline at end of file +output +pmail.db \ No newline at end of file diff --git a/README_CN.md b/README_CN.md index 98986e9e..40e0612f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -37,7 +37,11 @@ ## 1、生成DKIM 秘钥 -使用[go-msgauth](https://github.com/emersion/go-msgauth)项目的dkim-keygen工具生成公钥和私钥 +``` +go install github.com/emersion/go-msgauth/cmd/dkim-keygen@latest +dkim-keygen +``` +执行后将得到`dkim.priv`文件,公钥数据会直接输出 生成以后将密钥放到`config/dkim`目录中 diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 53059d40..d864bc68 --- a/build.sh +++ b/build.sh @@ -4,17 +4,14 @@ cd fe && yarn && yarn build # 编译后端代码 cd ../server && cp -rf ../fe/dist http_server -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 -go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_linux_amd64 main.go -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 -go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_windows_amd64 main.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_linux_amd64 main.go -CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 -go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_amd64 main.go +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_windows_amd64 main.go -CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 -go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_arm64 main.go +CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_amd64 main.go + +CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-X 'main.goVersion=$(go version)' -X 'main.gitHash=$(git show -s --format=%H)' -X 'main.buildTime=$(TZ=UTC-8 date +%Y-%m-%d" "%H:%M:%S)'" -o pmail_mac_arm64 main.go # 整理输出文件 cd .. diff --git a/fe/src/App.vue b/fe/src/App.vue index a52b2f9c..c42554e5 100644 --- a/fe/src/App.vue +++ b/fe/src/App.vue @@ -2,11 +2,11 @@ import { RouterView } from 'vue-router' import HomeHeader from '@/components/HomeHeader.vue' import HomeAside from '@/components/HomeAside.vue'; -import { watch,ref } from 'vue' +import { watch, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' const route = useRoute() -const pageName = ref(route.name) +const pageName = ref(route.name) watch( () => route.fullPath, @@ -21,7 +21,7 @@ watch(
-
+
diff --git a/fe/src/http/http.js b/fe/src/http/http.js index 093b8aa3..e7ffd826 100644 --- a/fe/src/http/http.js +++ b/fe/src/http/http.js @@ -9,7 +9,7 @@ var $http = axios.create({ timeout: 6000, //设置超时 headers: { 'Content-Type': 'application/json;charset=UTF-8;', - 'Lang':lang.lang + 'Lang': lang.lang } }) @@ -27,7 +27,7 @@ $http.interceptors.request.use((config) => { //响应拦截器 $http.interceptors.response.use((response) => { //响应成功 - if(response.data.errorNo == 403){ + if (response.data.errorNo == 403) { router.replace({ path: '/login', query: { @@ -35,6 +35,15 @@ $http.interceptors.response.use((response) => { } }) } + //响应成功 + if (response.data.errorNo == 402) { + router.replace({ + path: '/setup', + query: { + redirect: router.currentRoute.fullPath + } + }) + } return response.data; }, (error) => { //响应错误 diff --git a/fe/src/i18n/i18n.js b/fe/src/i18n/i18n.js index 9a45c791..72f4c178 100644 --- a/fe/src/i18n/i18n.js +++ b/fe/src/i18n/i18n.js @@ -18,7 +18,7 @@ var lang = { "cc_desc": "Cc's e-mail address", "send": "send", "add_att": "Add Attachment", - "attachment":"Attachment", + "attachment": "Attachment", "err_sender_must": "Sender's email prefix is required!", "only_prefix": "Only the email prefix is required!", "err_email_format": "Incorrect e-mail address, please check the e-mail format!", @@ -31,8 +31,28 @@ var lang = { "succ": "Success!", "err_pwd_diff": "The passwords entered twice do not match!", "fail": "Fail!", - "settings":"Settings", - "security":"Security" + "settings": "Settings", + "security": "Security", + "SetDomail": "Set Domain", + "setDNS": "Set DNS", + "setSSL": "Set SSL", + "setDatabase": "Set Database", + "setOther": "Other", + "welcome": "Welcome", + "next": "Next", + "tks_pmail": "Thanks for using Pmail", + "guid_desc": "Next, you will be guided to perform initial configuration. If you have already configured, please use your configuration file to overwrite the config folder of the running directory. If you have not configured it, please follow this guide.", + "select_db": "select database", + "db_desc": "PMail currently supports MySQL and SQLite3 databases, you can choose according to your needs.", + "type": "Type", + "db_select_ph": "please select your database", + "mysql_dsn": "MySQL DSN", + "sqlite_db_path": "Data File Path", + "domain_desc": "Set your domain infomation.", + "smtp_domain": "SMTP Domain", + "web_domain": "Web Domain", + "dns_desc": "Please add the following information to your DNS records", + }; @@ -57,7 +77,7 @@ var zhCN = { "cc_desc": "抄送人邮箱地址", "send": "发送", "add_att": "添加附件", - "attachment":"附件", + "attachment": "附件", "err_sender_must": "发件人邮箱前缀必填", "only_prefix": "只需要邮箱前缀", "err_email_format": "邮箱地址错误,请检查邮箱格式!", @@ -70,8 +90,27 @@ var zhCN = { "succ": "成功!", "err_pwd_diff": "两次输入的密码不一致!", "fail": "失败", - "settings":"设置", - "security":"安全" + "settings": "设置", + "security": "安全", + "SetDomail": "域名设置", + "setDNS": "DNS设置", + "setSSL": "SSL设置", + "setDatabase": "数据库设置", + "setOther": "其他设置", + "welcome": "欢迎", + "next": "下一步", + "tks_pmail": "感谢使用PMail", + "guid_desc": "接下来将会指引你进行初始化配置,如果你已有配置,请使用你的配置文件覆盖运行目录的config文件夹。如果你没有配置过,请按照该指引操作。", + "select_db": "数据库选择", + "db_desc": "PMail目前支持MySQL和SQLite3两种数据库,你可根据需要选择。", + "type": "类型", + "db_select_ph": "请选择你的数据库", + "mysql_dsn": "MySQL DSN", + "sqlite_db_path": "存储位置", + "domain_desc": "设置你的域名信息。", + "smtp_domain": "SMTP域名地址", + "web_domain": "Web域名地址", + "dns_desc": "请将以下信息添加到DNS记录中" } switch (navigator.language) { diff --git a/fe/src/router/index.js b/fe/src/router/index.js index 272d885b..26bc09bb 100644 --- a/fe/src/router/index.js +++ b/fe/src/router/index.js @@ -3,6 +3,9 @@ import ListView from '../views/ListView.vue' import EditerView from '../views/EditerView.vue' import LoginView from '../views/LoginView.vue' import EmailDetailView from '../views/EmailDetailView.vue' +import SetupView from '../views/SetupView.vue' + + const router = createRouter({ history: createWebHashHistory(import.meta.env.BASE_URL), routes: [ @@ -26,6 +29,11 @@ const router = createRouter({ name: "login", component: LoginView }, + { + path: '/setup', + name: "setup", + component: SetupView + }, { path: '/detail/:id', name: "detail", diff --git a/fe/src/views/EditerView.vue b/fe/src/views/EditerView.vue index 6ed1a03f..43404e56 100644 --- a/fe/src/views/EditerView.vue +++ b/fe/src/views/EditerView.vue @@ -76,6 +76,8 @@ + + + \ No newline at end of file diff --git a/server/config/config.dev.json b/server/config/config.dev.json index c1922336..f4b38917 100644 --- a/server/config/config.dev.json +++ b/server/config/config.dev.json @@ -1,11 +1,14 @@ { - "domain": "jinnrry.com", + "domain": "", + "webDomain": "", "dkimPrivateKeyPath": "config/dkim/dkim.priv", "SSLPrivateKeyPath": "config/ssl/private.key", "SSLPublicKeyPath": "config/ssl/public.crt", - "mysqlDSN": "", + "dbDSN": "", + "dbType": "", "weChatPushAppId": "", "weChatPushSecret": "", "weChatPushTemplateId": "", - "weChatPushUserId": "" + "weChatPushUserId": "", + "isInit": false } \ No newline at end of file diff --git a/server/config/config.go b/server/config/config.go index 96fe0d5b..a9b405be 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -8,25 +8,35 @@ import ( "strings" ) +var IsInit bool + type Config struct { - Domain string `json:"domain"` - DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"` - SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"` - SSLPublicKeyPath string `json:"SSLPublicKeyPath"` - MysqlDSN string `json:"mysqlDSN"` - - WeChatPushAppId string `json:"weChatPushAppId"` - WeChatPushSecret string `json:"weChatPushSecret"` - WeChatPushTemplateId string `json:"weChatPushTemplateId"` - WeChatPushUserId string `json:"weChatPushUserId"` - - Tables map[string]string - TablesInitData map[string]string + Domain string `json:"domain"` + WebDomain string `json:"webDomain"` + DkimPrivateKeyPath string `json:"dkimPrivateKeyPath"` + SSLPrivateKeyPath string `json:"SSLPrivateKeyPath"` + SSLPublicKeyPath string `json:"SSLPublicKeyPath"` + DbDSN string `json:"dbDSN"` + DbType string `json:"dbType"` + WeChatPushAppId string `json:"weChatPushAppId"` + WeChatPushSecret string `json:"weChatPushSecret"` + WeChatPushTemplateId string `json:"weChatPushTemplateId"` + WeChatPushUserId string `json:"weChatPushUserId"` + IsInit bool `json:"isInit"` + Tables map[string]string `json:"-"` + TablesInitData map[string]string `json:"-"` } //go:embed tables/* var tableConfig embed.FS +const Version = "1.1.0" + +const DBTypeMySQL = "mysql" +const DBTypeSQLite = "sqlite" + +var DBTypes []string = []string{DBTypeMySQL, DBTypeSQLite} + var Instance *Config func Init() { @@ -37,25 +47,29 @@ func Init() { if len(args) >= 2 && args[len(args)-1] == "dev" { cfgData, err = os.ReadFile("./config/config.dev.json") if err != nil { - panic("dev环境配置文件加载失败" + err.Error()) + return } } else { cfgData, err = os.ReadFile("./config/config.json") if err != nil { - panic("配置文件加载失败" + err.Error()) + return } } err = json.Unmarshal(cfgData, &Instance) if err != nil { - panic("配置文件加载失败" + err.Error()) + return } // 读取表设置 Instance.Tables = map[string]string{} Instance.TablesInitData = map[string]string{} - err = fs.WalkDir(tableConfig, "tables", func(path string, info fs.DirEntry, err error) error { + root := "tables/mysql" + if Instance.DbType == DBTypeSQLite { + root = "tables/sqlite" + } + err = fs.WalkDir(tableConfig, root, func(path string, info fs.DirEntry, err error) error { if !info.IsDir() && strings.HasSuffix(info.Name(), ".sql") { tableName := strings.ReplaceAll(info.Name(), ".sql", "") i, e := tableConfig.ReadFile(path) @@ -76,4 +90,8 @@ func Init() { panic(err) } + if Instance.Domain != "" && Instance.IsInit { + IsInit = true + } + } diff --git a/server/config/config.json b/server/config/config.json index 21cb7525..f4b38917 100644 --- a/server/config/config.json +++ b/server/config/config.json @@ -1,11 +1,14 @@ { - "domain": "demo.com", + "domain": "", + "webDomain": "", "dkimPrivateKeyPath": "config/dkim/dkim.priv", "SSLPrivateKeyPath": "config/ssl/private.key", "SSLPublicKeyPath": "config/ssl/public.crt", - "mysqlDSN": "", + "dbDSN": "", + "dbType": "", "weChatPushAppId": "", "weChatPushSecret": "", "weChatPushTemplateId": "", - "weChatPushUserId": "" + "weChatPushUserId": "", + "isInit": false } \ No newline at end of file diff --git a/server/config/tables/data/user.sql b/server/config/tables/mysql/data/user.sql similarity index 100% rename from server/config/tables/data/user.sql rename to server/config/tables/mysql/data/user.sql diff --git a/server/config/tables/data/user_auth.sql b/server/config/tables/mysql/data/user_auth.sql similarity index 100% rename from server/config/tables/data/user_auth.sql rename to server/config/tables/mysql/data/user_auth.sql diff --git a/server/config/tables/email.sql b/server/config/tables/mysql/email.sql similarity index 100% rename from server/config/tables/email.sql rename to server/config/tables/mysql/email.sql diff --git a/server/config/tables/sessions.sql b/server/config/tables/mysql/sessions.sql similarity index 100% rename from server/config/tables/sessions.sql rename to server/config/tables/mysql/sessions.sql diff --git a/server/config/tables/user.sql b/server/config/tables/mysql/user.sql similarity index 100% rename from server/config/tables/user.sql rename to server/config/tables/mysql/user.sql diff --git a/server/config/tables/user_auth.sql b/server/config/tables/mysql/user_auth.sql similarity index 100% rename from server/config/tables/user_auth.sql rename to server/config/tables/mysql/user_auth.sql diff --git a/server/config/tables/sqlite/data/user.sql b/server/config/tables/sqlite/data/user.sql new file mode 100644 index 00000000..b81caae6 --- /dev/null +++ b/server/config/tables/sqlite/data/user.sql @@ -0,0 +1,2 @@ +INSERT INTO user (account, name, password) VALUES ('admin', 'admin', 'faddb6ec2efe16116a342f5512583c48'); + diff --git a/server/config/tables/sqlite/data/user_auth.sql b/server/config/tables/sqlite/data/user_auth.sql new file mode 100644 index 00000000..7b5228e8 --- /dev/null +++ b/server/config/tables/sqlite/data/user_auth.sql @@ -0,0 +1,2 @@ +INSERT INTO user_auth (user_id, email_account) VALUES (1, '*'); + diff --git a/server/config/tables/sqlite/email.sql b/server/config/tables/sqlite/email.sql new file mode 100644 index 00000000..6dd35f24 --- /dev/null +++ b/server/config/tables/sqlite/email.sql @@ -0,0 +1,26 @@ +CREATE table email +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type tinyint(4) NOT NULL DEFAULT 0 , + subject varchar(1000) NOT NULL DEFAULT '' , + reply_to json , + from_name varchar(50) NOT NULL DEFAULT '' , + from_address varchar(150) NOT NULL DEFAULT '' , + `to` json , + bcc json , + cc json , + `text` text , + html text , + sender json , + attachments json , + spf_check tinyint(1) DEFAULT 0 , + dkim_check tinyint(1) DEFAULT 0 , + status tinyint(4) NOT NULL DEFAULT 0 , + send_user_id int unsigned NOT NULL DEFAULT 0 , + is_read tinyint(1) NOT NULL DEFAULT 0 , + error text , + cron_send_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP , + send_date datetime NOT NULL DEFAULT CURRENT_TIMESTAMP , + create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP , + update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP +) \ No newline at end of file diff --git a/server/config/tables/sqlite/sessions.sql b/server/config/tables/sqlite/sessions.sql new file mode 100644 index 00000000..198851fd --- /dev/null +++ b/server/config/tables/sqlite/sessions.sql @@ -0,0 +1,8 @@ +CREATE TABLE sessions +( + token TEXT PRIMARY KEY, + data BLOB NOT NULL, + expiry REAL NOT NULL +); + +CREATE INDEX sessions_expiry_idx ON sessions (expiry); \ No newline at end of file diff --git a/server/config/tables/sqlite/user.sql b/server/config/tables/sqlite/user.sql new file mode 100644 index 00000000..d08fb9eb --- /dev/null +++ b/server/config/tables/sqlite/user.sql @@ -0,0 +1,8 @@ +CREATE TABLE user +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + account varchar(20), + name varchar(10), + password char(32) +); +CREATE UNIQUE INDEX udx_account on user (account); \ No newline at end of file diff --git a/server/config/tables/sqlite/user_auth.sql b/server/config/tables/sqlite/user_auth.sql new file mode 100644 index 00000000..4205e0c8 --- /dev/null +++ b/server/config/tables/sqlite/user_auth.sql @@ -0,0 +1,9 @@ +CREATE TABLE user_auth +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id int , + email_account varchar(30) +); + +CREATE UNIQUE INDEX udx_uid_ename on user_auth ( user_id, email_account); +CREATE UNIQUE INDEX udx_ename_uid on user_auth ( email_account,user_id ); \ No newline at end of file diff --git a/server/controllers/email/send.go b/server/controllers/email/send.go index 73e98a50..c78c9a97 100644 --- a/server/controllers/email/send.go +++ b/server/controllers/email/send.go @@ -7,12 +7,12 @@ import ( "io" "net/http" "pmail/config" + "pmail/db" "pmail/dto" "pmail/dto/parsemail" "pmail/dto/response" "pmail/hooks" "pmail/i18n" - "pmail/mysql" "pmail/smtp_server" "pmail/utils/async" "strings" @@ -139,7 +139,7 @@ func Send(ctx *dto.Context, w http.ResponseWriter, req *http.Request) { // 邮件落库 sql := "INSERT INTO email (type,subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time,send_user_id,error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - sqlRes, sqlerr := mysql.Instance.Exec(mysql.WithContext(ctx, sql), + sqlRes, sqlerr := db.Instance.Exec(db.WithContext(ctx, sql), 1, e.Subject, json2string(e.ReplyTo), @@ -181,12 +181,12 @@ func Send(ctx *dto.Context, w http.ResponseWriter, req *http.Request) { if err != nil { errMsg = err.Error() - _, err := mysql.Instance.Exec(mysql.WithContext(ctx, "update email set status =2 ,error=? where id = ? "), errMsg, emailId) + _, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =2 ,error=? where id = ? "), errMsg, emailId) if err != nil { log.WithContext(ctx).Errorf("sql Error :%+v", err) } } else { - _, err := mysql.Instance.Exec(mysql.WithContext(ctx, "update email set status =1 where id = ? "), emailId) + _, err := db.Instance.Exec(db.WithContext(ctx, "update email set status =1 where id = ? "), emailId) if err != nil { log.WithContext(ctx).Errorf("sql Error :%+v", err) } diff --git a/server/controllers/login.go b/server/controllers/login.go index d44f7613..6c5a5a7b 100644 --- a/server/controllers/login.go +++ b/server/controllers/login.go @@ -8,11 +8,11 @@ import ( log "github.com/sirupsen/logrus" "io" "net/http" + "pmail/db" "pmail/dto" "pmail/dto/response" "pmail/i18n" "pmail/models" - "pmail/mysql" "pmail/session" ) @@ -27,18 +27,18 @@ func Login(ctx *dto.Context, w http.ResponseWriter, req *http.Request) { if err != nil { log.Errorf("%+v", err) } - var retData loginRequest - err = json.Unmarshal(reqBytes, &retData) + var reqData loginRequest + err = json.Unmarshal(reqBytes, &reqData) if err != nil { log.Errorf("%+v", err) } var user models.User - encodePwd := md5Encode(md5Encode(retData.Password+"pmail") + "pmail2023") + encodePwd := md5Encode(md5Encode(reqData.Password+"pmail") + "pmail2023") - err = mysql.Instance.Get(&user, mysql.WithContext(ctx, "select * from user where account =? and password =?"), - retData.Account, encodePwd) + err = db.Instance.Get(&user, db.WithContext(ctx, "select * from user where account =? and password =?"), + reqData.Account, encodePwd) if err != nil && err != sql.ErrNoRows { log.Errorf("%+v", err) } diff --git a/server/controllers/settings.go b/server/controllers/settings.go index 96acdfbe..47297c8e 100644 --- a/server/controllers/settings.go +++ b/server/controllers/settings.go @@ -5,10 +5,10 @@ import ( log "github.com/sirupsen/logrus" "io" "net/http" + "pmail/db" "pmail/dto" "pmail/dto/response" "pmail/i18n" - "pmail/mysql" ) type modifyPasswordRequest struct { @@ -29,7 +29,7 @@ func ModifyPassword(ctx *dto.Context, w http.ResponseWriter, req *http.Request) if retData.Password != "" { encodePwd := md5Encode(md5Encode(retData.Password+"pmail") + "pmail2023") - _, err := mysql.Instance.Exec(mysql.WithContext(ctx, "update user set password = ? where id =?"), encodePwd, ctx.UserInfo.ID) + _, err := db.Instance.Exec(db.WithContext(ctx, "update user set password = ? where id =?"), encodePwd, ctx.UserInfo.ID) if err != nil { response.NewErrorResponse(response.ServerError, i18n.GetText(ctx.Lang, "unknowError"), "").FPrint(w) return diff --git a/server/controllers/setup.go b/server/controllers/setup.go new file mode 100644 index 00000000..6eaebd8f --- /dev/null +++ b/server/controllers/setup.go @@ -0,0 +1,92 @@ +package controllers + +import ( + "encoding/json" + "io" + "net/http" + "pmail/dto" + "pmail/dto/response" + "pmail/services/setup" +) + +func Proxy(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("proxy")) +} + +func Setup(ctx *dto.Context, w http.ResponseWriter, req *http.Request) { + reqBytes, err := io.ReadAll(req.Body) + if err != nil { + response.NewSuccessResponse("").FPrint(w) + return + } + + var reqData map[string]string + err = json.Unmarshal(reqBytes, &reqData) + + if err != nil { + response.NewSuccessResponse("").FPrint(w) + return + } + + if reqData["step"] == "database" && reqData["action"] == "get" { + dbType, dbDSN, err := setup.GetDatabaseSettings() + if err != nil { + response.NewErrorResponse(response.ServerError, err.Error(), "") + } + + response.NewSuccessResponse(map[string]string{ + "db_type": dbType, + "db_dsn": dbDSN, + }).FPrint(w) + return + } + + if reqData["step"] == "database" && reqData["action"] == "set" { + err := setup.SetDatabaseSettings(reqData["db_type"], reqData["db_dsn"]) + if err != nil { + response.NewErrorResponse(response.ServerError, err.Error(), "") + } + + response.NewSuccessResponse("Succ").FPrint(w) + return + } + + if reqData["step"] == "domain" && reqData["action"] == "get" { + smtpDomain, webDomain, err := setup.GetDomainSettings() + if err != nil { + response.NewErrorResponse(response.ServerError, err.Error(), "") + } + response.NewSuccessResponse(map[string]string{ + "smtp_domain": smtpDomain, + "web_domain": webDomain, + }).FPrint(w) + return + } + + if reqData["step"] == "domain" && reqData["action"] == "set" { + err := setup.SetDomainSettings(reqData["smtp_domain"], reqData["web_domain"]) + if err != nil { + response.NewErrorResponse(response.ServerError, err.Error(), "") + } + response.NewSuccessResponse("Succ").FPrint(w) + return + } + + if reqData["step"] == "dns" && reqData["action"] == "get" { + dnsInfos, err := setup.GetDNSSettings(ctx) + if err != nil { + response.NewErrorResponse(response.ServerError, err.Error(), "") + } + response.NewSuccessResponse(dnsInfos).FPrint(w) + return + } + + if reqData["step"] == "ssl" && reqData["action"] == "get" { + err := setup.GenSSL() + if err != nil { + response.NewErrorResponse(response.ServerError, err.Error(), "") + } + response.NewSuccessResponse("").FPrint(w) + return + } +} diff --git a/server/mysql/init.go b/server/db/init.go similarity index 77% rename from server/mysql/init.go rename to server/db/init.go index eaf95160..766cab11 100644 --- a/server/mysql/init.go +++ b/server/db/init.go @@ -1,10 +1,11 @@ -package mysql +package db import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" + _ "modernc.org/sqlite" "pmail/config" "pmail/dto" ) @@ -12,15 +13,23 @@ import ( var Instance *sqlx.DB func Init() { - dsn := config.Instance.MysqlDSN + dsn := config.Instance.DbDSN var err error - Instance, err = sqlx.Open("mysql", dsn) + + switch config.Instance.DbType { + case "mysql": + Instance, err = sqlx.Open("mysql", dsn) + case "sqlite": + Instance, err = sqlx.Open("sqlite", dsn) + default: + return + } if err != nil { panic(err) } Instance.SetMaxOpenConns(100) Instance.SetMaxIdleConns(10) - showMySQLCharacterSet() + //showMySQLCharacterSet() checkTable() } @@ -38,7 +47,13 @@ type tables struct { func checkTable() { var res []*tables - err := Instance.Select(&res, "show tables") + + var err error + if config.Instance.DbType == "sqlite" { + err = Instance.Select(&res, "select name as `Tables_in_pmail` from sqlite_master where type='table'") + } else { + err = Instance.Select(&res, "show tables") + } if err != nil { panic(err) } diff --git a/server/dto/response/response.go b/server/dto/response/response.go index 25a91585..fdd7cea6 100644 --- a/server/dto/response/response.go +++ b/server/dto/response/response.go @@ -6,6 +6,7 @@ import ( ) const ( + NeedSetup = 402 NeedLogin = 403 ParamsError = 100 ServerError = 500 diff --git a/server/go.mod b/server/go.mod index 7660e7f4..7a22e8af 100644 --- a/server/go.mod +++ b/server/go.mod @@ -4,25 +4,47 @@ go 1.20 require ( github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24 + github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24 github.com/alexedwards/scs/v2 v2.5.1 + github.com/emersion/go-message v0.16.0 github.com/emersion/go-msgauth v0.6.6 github.com/emersion/go-smtp v0.16.0 + github.com/go-acme/lego/v4 v4.13.3 github.com/go-sql-driver/mysql v1.7.1 github.com/jmoiron/sqlx v1.3.5 github.com/mileusna/spf v0.9.5 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cast v1.5.1 - golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 - golang.org/x/text v0.3.8 + golang.org/x/crypto v0.10.0 + golang.org/x/text v0.10.0 + modernc.org/sqlite v1.24.0 ) +replace github.com/alexedwards/scs/sqlite3store v0.0.0-20230327161757-10d4299e3b24 => github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379 + require ( - github.com/emersion/go-message v0.16.0 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect - github.com/miekg/dns v1.1.50 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/tools v0.1.12 // indirect + github.com/go-jose/go-jose/v3 v3.0.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/miekg/dns v1.1.55 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/mod v0.11.0 // indirect + golang.org/x/net v0.11.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/tools v0.10.0 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect ) diff --git a/server/go.sum b/server/go.sum index e7d27ec7..611cc213 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,10 +1,16 @@ +github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379 h1:i6LB/3lgkRDupe3owyNXtH8dtQrdaReCLeAZKrWcqAE= +github.com/Jinnrry/scs/sqlite3store v0.0.0-20230803080525-914f01e0d379/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss= github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24 h1:1jXpX7IE/zuf9FZQJpqZNepXqW8mq6NLzplHDCA43HY= github.com/alexedwards/scs/mysqlstore v0.0.0-20230327161757-10d4299e3b24/go.mod h1:ShejCOaSJCEjCWjc7YBrgy2xd0Kp+wiyBdzTNQrAGn4= github.com/alexedwards/scs/v2 v2.5.1 h1:EhAz3Kb3OSQzD8T+Ub23fKsiuvE0GzbF5Lgn0uTwM3Y= github.com/alexedwards/scs/v2 v2.5.1/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emersion/go-message v0.11.2/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= github.com/emersion/go-message v0.16.0 h1:uZLz8ClLv3V5fSFF/fFdW9jXjrZkXIpE1Fn8fKx7pO4= @@ -20,26 +26,43 @@ github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1: github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/go-acme/lego/v4 v4.13.3 h1:aZ1S9FXIkCWG3Uw/rZKSD+MOuO8ZB1t6p9VCg6jJiNY= +github.com/go-acme/lego/v4 v4.13.3/go.mod h1:c/iodVGMeBXG/+KiQczoNkySo3YLWTVa0kiyeVd/FHc= +github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= +github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -47,26 +70,29 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 h1:SLP7Q4Di66FONjDJbCYrCRrh97focO6sLogHO7/g8F0= golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -76,24 +102,50 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI= +modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= diff --git a/server/http_server/main.go b/server/http_server/main.go index 815e130a..a644d81e 100644 --- a/server/http_server/main.go +++ b/server/http_server/main.go @@ -13,10 +13,12 @@ import ( "net" "net/http" "os" + "pmail/config" "pmail/controllers" "pmail/controllers/email" "pmail/dto" "pmail/dto/response" + "pmail/i18n" "pmail/session" "time" ) @@ -28,6 +30,37 @@ var ip string const HttpPort = 80 +var setupServer *http.Server + +func SetupStart() { + mux := http.NewServeMux() + fe, err := fs.Sub(local, "dist") + if err != nil { + panic(err) + } + mux.Handle("/", http.FileServer(http.FS(fe))) + mux.HandleFunc("/api/", contextIterceptor(controllers.Setup)) + mux.HandleFunc("/", controllers.Proxy) + + setupServer := &http.Server{ + Addr: fmt.Sprintf(":%d", HttpPort), + Handler: mux, + ReadTimeout: time.Second * 60, + WriteTimeout: time.Second * 60, + } + err = setupServer.ListenAndServe() + if err != nil { + panic(err) + } +} + +func SetupStop() { + err := setupServer.Close() + if err != nil { + panic(err) + } +} + func Start() { log.Infof("Http Server Start at :%d", HttpPort) @@ -118,15 +151,20 @@ func contextIterceptor(h controllers.HandlerFunc) http.HandlerFunc { } ctx.Lang = lang - user := cast.ToString(session.Instance.Get(ctx, "user")) - if user != "" { - _ = json.Unmarshal([]byte(user), &ctx.UserInfo) - } - if ctx.UserInfo == nil || ctx.UserInfo.ID == 0 { - if r.URL.Path != "/api/ping" && r.URL.Path != "/api/login" { - response.NewErrorResponse(response.NeedLogin, "登陆已失效!", "").FPrint(w) - return + if config.IsInit { + user := cast.ToString(session.Instance.Get(ctx, "user")) + if user != "" { + _ = json.Unmarshal([]byte(user), &ctx.UserInfo) } + if ctx.UserInfo == nil || ctx.UserInfo.ID == 0 { + if r.URL.Path != "/api/ping" && r.URL.Path != "/api/login" { + response.NewErrorResponse(response.ParamsError, i18n.GetText(ctx.Lang, "login_exp"), "").FPrint(w) + return + } + } + } else if r.URL.Path != "/api/setup" { + response.NewErrorResponse(response.NeedSetup, "", "").FPrint(w) + return } h(ctx, w, r) } diff --git a/server/i18n/i18n.go b/server/i18n/i18n.go index 395de5e8..b0a60cdc 100644 --- a/server/i18n/i18n.go +++ b/server/i18n/i18n.go @@ -11,6 +11,8 @@ var ( "succ": "成功", "send_fail": "发送失败", "att_err": "附件解码错误", + "login_exp": "登录已失效", + "ip_taps": "这是你服务器IP,确保这个IP正确", } en = map[string]string{ "all_email": "All Email", @@ -22,6 +24,8 @@ var ( "succ": "Success", "send_fail": "Send Failure", "att_err": "Attachment decoding error", + "login_exp": "Login has expired.", + "ip_taps": "This is your server's IP, make sure it is correct.", } ) diff --git a/server/main.go b/server/main.go index b23a83ee..b874865e 100644 --- a/server/main.go +++ b/server/main.go @@ -7,12 +7,7 @@ import ( "os" "pmail/config" "pmail/dto" - "pmail/dto/parsemail" - "pmail/hooks" - "pmail/http_server" - "pmail/mysql" - "pmail/session" - "pmail/smtp_server" + "pmail/res_init" "time" ) @@ -57,20 +52,10 @@ func main() { var cst, _ = time.LoadLocation("Asia/Shanghai") time.Local = cst - config.Init() - parsemail.Init() - mysql.Init() - session.Init() - hooks.Init() - - // smtp server start - go smtp_server.Start() - - // http server start - go http_server.Start() + res_init.Init() log.Infoln("***************************************************") - log.Infoln("***\tServer Start Success Version:1.0.0") + log.Infof("***\tServer Start Success Version:%s\n", config.Version) log.Infof("***\tGit Commit Hash: %s ", gitHash) log.Infof("***\tBuild TimeStamp: %s ", buildTime) log.Infof("***\tBuild GoLang Version: %s ", goVersion) diff --git a/server/res_init/init.go b/server/res_init/init.go new file mode 100644 index 00000000..7dd12d01 --- /dev/null +++ b/server/res_init/init.go @@ -0,0 +1,54 @@ +package res_init + +import ( + "os" + "pmail/config" + "pmail/db" + "pmail/dto/parsemail" + "pmail/hooks" + "pmail/http_server" + "pmail/session" + "pmail/smtp_server" + "pmail/utils/file" +) + +func Init() { + config.Init() + + if config.IsInit { + parsemail.Init() + db.Init() + session.Init() + hooks.Init() + // smtp server start + go smtp_server.Start() + // http server start + go http_server.Start() + } else { + dirInit() + go http_server.SetupStart() + } +} + +func dirInit() { + if !file.PathExist("./config") { + err := os.MkdirAll("./config", 0744) + if err != nil { + panic(err) + } + } + + if !file.PathExist("./config/dkim") { + err := os.MkdirAll("./config/dkim", 0744) + if err != nil { + panic(err) + } + } + + if !file.PathExist("./config/ssl") { + err := os.MkdirAll("./config/ssl", 0744) + if err != nil { + panic(err) + } + } +} diff --git a/server/services/attachments/attachments.go b/server/services/attachments/attachments.go index 7bac9615..a1c818a6 100644 --- a/server/services/attachments/attachments.go +++ b/server/services/attachments/attachments.go @@ -3,10 +3,10 @@ package attachments import ( "encoding/json" log "github.com/sirupsen/logrus" + "pmail/db" "pmail/dto" "pmail/dto/parsemail" "pmail/models" - "pmail/mysql" "pmail/services/auth" ) @@ -14,7 +14,7 @@ func GetAttachments(ctx *dto.Context, emailId int, cid string) (string, []byte) // 获取邮件内容 var email models.Email - err := mysql.Instance.Get(&email, mysql.WithContext(ctx, "select * from email where id = ?"), emailId) + err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), emailId) if err != nil { log.WithContext(ctx).Errorf("SQL error:%+v", err) return "", nil @@ -39,7 +39,7 @@ func GetAttachmentsByIndex(ctx *dto.Context, emailId int, index int) (string, [] // 获取邮件内容 var email models.Email - err := mysql.Instance.Get(&email, mysql.WithContext(ctx, "select * from email where id = ?"), emailId) + err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), emailId) if err != nil { log.WithContext(ctx).Errorf("SQL error:%+v", err) return "", nil diff --git a/server/services/auth/auth.go b/server/services/auth/auth.go index 50b80ebb..cad1076f 100644 --- a/server/services/auth/auth.go +++ b/server/services/auth/auth.go @@ -1,10 +1,17 @@ package auth import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" log "github.com/sirupsen/logrus" + "os" + "pmail/db" "pmail/dto" "pmail/models" - "pmail/mysql" "strings" ) @@ -12,7 +19,7 @@ import ( func HasAuth(ctx *dto.Context, email *models.Email) bool { // 获取当前用户的auth var auth []models.UserAuth - err := mysql.Instance.Select(&auth, mysql.WithContext(ctx, "select * from user_auth where user_id = ?"), ctx.UserInfo.ID) + err := db.Instance.Select(&auth, db.WithContext(ctx, "select * from user_auth where user_id = ?"), ctx.UserInfo.ID) if err != nil { log.WithContext(ctx).Errorf("SQL error:%+v", err) return false @@ -31,3 +38,74 @@ func HasAuth(ctx *dto.Context, email *models.Email) bool { return hasAuth } + +func DkimGen() string { + privKeyStr, _ := os.ReadFile("./config/dkim/dkim.priv") + publicKeyStr, _ := os.ReadFile("./config/dkim/dkim.public") + if len(privKeyStr) > 0 && len(publicKeyStr) > 0 { + return string(publicKeyStr) + } + + var ( + privKey crypto.Signer + err error + ) + + privKey, err = rsa.GenerateKey(rand.Reader, 3072) + + if err != nil { + log.Fatalf("Failed to generate key: %v", err) + } + + privBytes, err := x509.MarshalPKCS8PrivateKey(privKey) + if err != nil { + log.Fatalf("Failed to marshal private key: %v", err) + } + + f, err := os.OpenFile("./config/dkim/dkim.priv", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("Failed to create key file: %v", err) + } + defer f.Close() + + privBlock := pem.Block{ + Type: "PRIVATE KEY", + Bytes: privBytes, + } + if err := pem.Encode(f, &privBlock); err != nil { + log.Fatalf("Failed to write key PEM block: %v", err) + } + if err := f.Close(); err != nil { + log.Fatalf("Failed to close key file: %v", err) + } + + var pubBytes []byte + + switch pubKey := privKey.Public().(type) { + case *rsa.PublicKey: + // RFC 6376 is inconsistent about whether RSA public keys should + // be formatted as RSAPublicKey or SubjectPublicKeyInfo. + // Erratum 3017 (https://www.rfc-editor.org/errata/eid3017) + // proposes allowing both. We use SubjectPublicKeyInfo for + // consistency with other implementations including opendkim, + // Gmail, and Fastmail. + pubBytes, err = x509.MarshalPKIXPublicKey(pubKey) + if err != nil { + log.Fatalf("Failed to marshal public key: %v", err) + } + default: + panic("unreachable") + } + + params := []string{ + "v=DKIM1", + "k=rsa", + "p=" + base64.StdEncoding.EncodeToString(pubBytes), + } + + publicKey := strings.Join(params, "; ") + + os.WriteFile("./config/dkim/dkim.public", []byte(publicKey), 0666) + + return publicKey +} diff --git a/server/services/detail/detail.go b/server/services/detail/detail.go index ce0457cc..8ae9a530 100644 --- a/server/services/detail/detail.go +++ b/server/services/detail/detail.go @@ -5,24 +5,24 @@ import ( "encoding/json" "fmt" log "github.com/sirupsen/logrus" + "pmail/db" "pmail/dto" "pmail/dto/parsemail" "pmail/models" - "pmail/mysql" "strings" ) func GetEmailDetail(ctx *dto.Context, id int, markRead bool) (*models.Email, error) { // 获取邮件内容 var email models.Email - err := mysql.Instance.Get(&email, mysql.WithContext(ctx, "select * from email where id = ?"), id) + err := db.Instance.Get(&email, db.WithContext(ctx, "select * from email where id = ?"), id) if err != nil { log.WithContext(ctx).Errorf("SQL error:%+v", err) return nil, err } if markRead && email.IsRead == 0 { - _, err = mysql.Instance.Exec(mysql.WithContext(ctx, "update email set is_read =1 where id =?"), email.Id) + _, err = db.Instance.Exec(db.WithContext(ctx, "update email set is_read =1 where id =?"), email.Id) if err != nil { log.WithContext(ctx).Errorf("SQL error:%+v", err) } diff --git a/server/services/list/list.go b/server/services/list/list.go index 8eb6984e..23fbaeed 100644 --- a/server/services/list/list.go +++ b/server/services/list/list.go @@ -3,9 +3,9 @@ package list import ( "encoding/json" log "github.com/sirupsen/logrus" + "pmail/db" "pmail/dto" "pmail/models" - "pmail/mysql" ) func GetEmailList(ctx *dto.Context, tag string, keyword string, offset, limit int) (emailList []*models.Email, total int) { @@ -13,12 +13,12 @@ func GetEmailList(ctx *dto.Context, tag string, keyword string, offset, limit in querySQL, queryParams := genSQL(ctx, false, tag, keyword, offset, limit) counterSQL, counterParams := genSQL(ctx, true, tag, keyword, offset, limit) - err := mysql.Instance.Select(&emailList, querySQL, queryParams...) + err := db.Instance.Select(&emailList, querySQL, queryParams...) if err != nil { log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err) } - err = mysql.Instance.Get(&total, counterSQL, counterParams...) + err = db.Instance.Get(&total, counterSQL, counterParams...) if err != nil { log.Errorf("SQL ERROR: %s ,Error:%s", querySQL, err) } diff --git a/server/services/setup/db.go b/server/services/setup/db.go new file mode 100644 index 00000000..dfd35413 --- /dev/null +++ b/server/services/setup/db.go @@ -0,0 +1,76 @@ +package setup + +import ( + "encoding/json" + "os" + "pmail/config" + "pmail/utils/array" + "pmail/utils/errors" + "pmail/utils/file" +) + +func GetDatabaseSettings() (string, string, error) { + configData, err := readConfig() + if err != nil { + return "", "", errors.Wrap(err) + } + + return configData.DbType, configData.DbDSN, nil +} + +func SetDatabaseSettings(dbType, dbDSN string) error { + configData, err := readConfig() + if err != nil { + return errors.Wrap(err) + } + + if !array.InArray(dbType, config.DBTypes) { + return errors.New("dbtype error") + } + + configData.DbType = dbType + configData.DbDSN = dbDSN + + // 检查数据库是否能正确连接 todo + + err = writeConfig(configData) + if err != nil { + return errors.Wrap(err) + } + return nil +} + +func writeConfig(cfg *config.Config) error { + bytes, _ := json.Marshal(cfg) + err := os.WriteFile("./config/config.json", bytes, 0666) + if err != nil { + return errors.Wrap(err) + } + return nil +} + +func readConfig() (*config.Config, error) { + configData := config.Config{ + DkimPrivateKeyPath: "config/dkim/dkim.priv", + SSLPrivateKeyPath: "config/ssl/private.key", + SSLPublicKeyPath: "config/ssl/public.crt", + } + if !file.PathExist("./config/config.json") { + bytes, _ := json.Marshal(configData) + err := os.WriteFile("./config/config.json", bytes, 0666) + if err != nil { + return nil, errors.Wrap(err) + } + } else { + cfgData, err := os.ReadFile("./config/config.json") + if err != nil { + return nil, errors.Wrap(err) + } + + err = json.Unmarshal(cfgData, &configData) + if err != nil { + return nil, errors.Wrap(err) + } + } + return &configData, nil +} diff --git a/server/services/setup/dns.go b/server/services/setup/dns.go new file mode 100644 index 00000000..8fbbbcf2 --- /dev/null +++ b/server/services/setup/dns.go @@ -0,0 +1,54 @@ +package setup + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "pmail/dto" + "pmail/i18n" + "pmail/services/auth" + "pmail/utils/errors" +) + +type DNSItem struct { + Type string `json:"type"` + Host string `json:"host"` + Value string `json:"value"` + TTL int `json:"ttl"` + Tips string `json:"tips"` +} + +func GetDNSSettings(ctx *dto.Context) ([]*DNSItem, error) { + configData, err := readConfig() + if err != nil { + return nil, errors.Wrap(err) + } + + ret := []*DNSItem{ + {Type: "A", Host: "smtp", Value: getIp(), TTL: 3600, Tips: i18n.GetText(ctx.Lang, "ip_taps")}, + {Type: "MX", Host: "-", Value: fmt.Sprintf("smtp.%s", configData.Domain), TTL: 3600}, + {Type: "TXT", Host: "-", Value: "v=spf1 a mx ~all", TTL: 3600}, + {Type: "TXT", Host: "default._domainkey", Value: auth.DkimGen(), TTL: 3600}, + } + return ret, nil +} + +func getIp() string { + resp, err := http.Get("http://ip-api.com/json/?lang=zh-CN ") + if err != nil { + return "Your Server IP" + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + body, err := io.ReadAll(resp.Body) + if err == nil { + var queryRes map[string]string + _ = json.Unmarshal(body, &queryRes) + + return queryRes["query"] + } + } + return "Your Server IP" +} diff --git a/server/services/setup/dns_test.go b/server/services/setup/dns_test.go new file mode 100644 index 00000000..93d40dc4 --- /dev/null +++ b/server/services/setup/dns_test.go @@ -0,0 +1,7 @@ +package setup + +import "testing" + +func TestGetIp(t *testing.T) { + GetIp() +} diff --git a/server/services/setup/domain.go b/server/services/setup/domain.go new file mode 100644 index 00000000..6d2bafc1 --- /dev/null +++ b/server/services/setup/domain.go @@ -0,0 +1,32 @@ +package setup + +import ( + "pmail/utils/errors" +) + +func GetDomainSettings() (string, string, error) { + configData, err := readConfig() + if err != nil { + return "", "", errors.Wrap(err) + } + + return configData.Domain, configData.WebDomain, nil +} + +func SetDomainSettings(smtpDomain, webDomain string) error { + configData, err := readConfig() + if err != nil { + return errors.Wrap(err) + } + + configData.Domain = smtpDomain + configData.WebDomain = webDomain + + // 检查域名是否指向本机 todo + + err = writeConfig(configData) + if err != nil { + return errors.Wrap(err) + } + return nil +} diff --git a/server/services/setup/ssl.go b/server/services/setup/ssl.go new file mode 100644 index 00000000..546dcded --- /dev/null +++ b/server/services/setup/ssl.go @@ -0,0 +1,103 @@ +package setup + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + log "github.com/sirupsen/logrus" + "pmail/utils/errors" + + "github.com/go-acme/lego/v4/certcrypto" + "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/challenge/http01" + "github.com/go-acme/lego/v4/challenge/tlsalpn01" + "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/registration" +) + +type MyUser struct { + Email string + Registration *registration.Resource + key crypto.PrivateKey +} + +func (u *MyUser) GetEmail() string { + return u.Email +} +func (u MyUser) GetRegistration() *registration.Resource { + return u.Registration +} +func (u *MyUser) GetPrivateKey() crypto.PrivateKey { + return u.key +} + +func GenSSL() error { + + configData, err := readConfig() + if err != nil { + return errors.Wrap(err) + } + + // Create a user. New accounts need an email and private key to start. + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + log.Fatal(err) + } + + myUser := MyUser{ + Email: "i@" + configData.Domain, + key: privateKey, + } + + config := lego.NewConfig(&myUser) + + config.Certificate.KeyType = certcrypto.RSA2048 + + // A client facilitates communication with the CA server. + client, err := lego.NewClient(config) + if err != nil { + log.Fatal(err) + } + + // We specify an HTTP port of 5002 and an TLS port of 5001 on all interfaces + // because we aren't running as root and can't bind a listener to port 80 and 443 + // (used later when we attempt to pass challenges). Keep in mind that you still + // need to proxy challenge traffic to port 5002 and 5001. + err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5001")) + if err != nil { + log.Fatal(err) + } + err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "443")) + if err != nil { + log.Fatal(err) + } + + // New users will need to register + reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + if err != nil { + log.Fatal(err) + } + myUser.Registration = reg + + request := certificate.ObtainRequest{ + Domains: []string{ + fmt.Sprintf("smtp.%s", configData.Domain), + configData.WebDomain, + }, + Bundle: true, + } + certificates, err := client.Certificate.Obtain(request) + if err != nil { + log.Fatal(err) + } + + // Each certificate comes back with the cert bytes, the bytes of the client's + // private key, and a certificate URL. SAVE THESE TO DISK. + fmt.Printf("%#v\n", certificates) + + // ... all done. + + return nil +} diff --git a/server/session/init.go b/server/session/init.go index fbde8454..54e953ea 100644 --- a/server/session/init.go +++ b/server/session/init.go @@ -2,8 +2,10 @@ package session import ( "github.com/alexedwards/scs/mysqlstore" + "github.com/alexedwards/scs/sqlite3store" "github.com/alexedwards/scs/v2" - "pmail/mysql" + "pmail/config" + "pmail/db" "time" ) @@ -13,7 +15,11 @@ var Instance *scs.SessionManager func Init() { Instance = scs.New() Instance.Lifetime = 24 * time.Hour - // 使用mysql存储session数据,目前为了架构简单, + // 使用db存储session数据,目前为了架构简单, // 暂不引入redis存储,如果日后性能存在瓶颈,可以将session迁移到redis - Instance.Store = mysqlstore.New(mysql.Instance.DB) + if config.Instance.DbType == "mysql" { + Instance.Store = mysqlstore.New(db.Instance.DB) + } else { + Instance.Store = sqlite3store.New(db.Instance.DB) + } } diff --git a/server/smtp_server/read_content.go b/server/smtp_server/read_content.go index d4304902..1f042a58 100644 --- a/server/smtp_server/read_content.go +++ b/server/smtp_server/read_content.go @@ -8,9 +8,9 @@ import ( "io" "net" "net/netip" + "pmail/db" "pmail/dto/parsemail" "pmail/hooks" - "pmail/mysql" "pmail/utils/async" "strings" "time" @@ -65,7 +65,7 @@ func (s *Session) Data(r io.Reader) error { } sql := "INSERT INTO email (send_date, subject, reply_to, from_name, from_address, `to`, bcc, cc, text, html, sender, attachments,spf_check, dkim_check, create_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - _, err = mysql.Instance.Exec(sql, + _, err = db.Instance.Exec(sql, email.Date, email.Subject, json2string(email.ReplyTo), diff --git a/server/smtp_server/read_content_test.go b/server/smtp_server/read_content_test.go index 756f7998..9620d90b 100644 --- a/server/smtp_server/read_content_test.go +++ b/server/smtp_server/read_content_test.go @@ -9,8 +9,8 @@ import ( "os" "path/filepath" "pmail/config" + "pmail/db" parsemail2 "pmail/dto/parsemail" - "pmail/mysql" "pmail/session" "testing" "time" @@ -39,7 +39,7 @@ func testInit() { config.Init() parsemail2.Init() - mysql.Init() + db.Init() session.Init() } diff --git a/server/utils/array/array.go b/server/utils/array/array.go index 6721a675..e121799c 100644 --- a/server/utils/array/array.go +++ b/server/utils/array/array.go @@ -17,3 +17,78 @@ func Join[T any](arg []T, str string) string { } return ret.String() } + +// Unique 数组去重 +func Unique[T comparable](slice []T) []T { + mp := map[T]bool{} + for _, v := range slice { + mp[v] = true + } + ret := []T{} + for t, _ := range mp { + ret = append(ret, t) + } + return ret +} + +// Merge 求并集 +func Merge[T any](slice1, slice2 []T) []T { + s1Len := len(slice1) + + slice3 := make([]T, s1Len+len(slice2)) + for i, t := range slice1 { + slice3[i] = t + } + + for i, t := range slice2 { + slice3[s1Len+i] = t + } + + return slice3 +} + +// Intersect 求交集 +func Intersect[T comparable](slice1, slice2 []T) []T { + m := make(map[T]bool) + nn := make([]T, 0) + for _, v := range slice1 { + m[v] = true + } + + for _, v := range slice2 { + exist, _ := m[v] + if exist { + nn = append(nn, v) + } + } + return nn +} + +// Difference 求差集 slice1-并集 +func Difference[T comparable](slice1, slice2 []T) []T { + m := make(map[T]bool) + nn := make([]T, 0) + inter := Intersect(slice1, slice2) + for _, v := range inter { + m[v] = true + } + + for _, value := range slice1 { + exist, _ := m[value] + if !exist { + nn = append(nn, value) + } + } + return nn +} + +// InArray 判断元素是否在数组中 +func InArray[T comparable](needle T, haystack []T) bool { + for _, t := range haystack { + if needle == t { + return true + } + } + + return false +} diff --git a/server/utils/errors/error.go b/server/utils/errors/error.go new file mode 100644 index 00000000..f5648fa4 --- /dev/null +++ b/server/utils/errors/error.go @@ -0,0 +1,34 @@ +package errors + +import ( + oe "errors" + "fmt" + "runtime" +) + +func New(text string) error { + _, file, line, _ := runtime.Caller(1) + return oe.New(fmt.Sprintf("%s at %s:%d", text, file, line)) +} + +func Wrap(err error) error { + _, file, line, _ := runtime.Caller(1) + return fmt.Errorf("at %s:%d\n%w", file, line, err) +} + +func WrapWithMsg(err error, msg string) error { + _, file, line, _ := runtime.Caller(1) + return fmt.Errorf("%s at %s:%d\n%w", msg, file, line, err) +} + +func Unwrap(err error) error { + return oe.Unwrap(err) +} + +func Is(err, target error) bool { + return oe.Is(err, target) +} + +func As(err error, target any) bool { + return oe.As(err, target) +} diff --git a/server/utils/errors/error_test.go b/server/utils/errors/error_test.go new file mode 100644 index 00000000..bff8d8a0 --- /dev/null +++ b/server/utils/errors/error_test.go @@ -0,0 +1,29 @@ +package errors + +import ( + "fmt" + "testing" +) + +func TestNew(t *testing.T) { + err := New("err") + fmt.Println(err) +} + +func TestWarp(t *testing.T) { + err := New("err1") + err = Wrap(err) + err = Wrap(err) + err = Wrap(err) + err = Wrap(err) + fmt.Println(err) +} + +func TestWarpWithMsg(t *testing.T) { + err := New("err1") + err = Wrap(err) + err = Wrap(err) + err = Wrap(err) + err = WrapWithMsg(err, "last") + fmt.Println(err) +} diff --git a/server/utils/file/file.go b/server/utils/file/file.go new file mode 100644 index 00000000..eb14829e --- /dev/null +++ b/server/utils/file/file.go @@ -0,0 +1,11 @@ +package file + +import "os" + +func PathExist(_path string) bool { + _, err := os.Stat(_path) + if err != nil && os.IsNotExist(err) { + return false + } + return true +}