package main
import (
"bytes"
"encoding/gob"
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/utils"
)
/*
SMTP Settings and Test Email Handlers
*/
func HandleSMTPSet(w http.ResponseWriter, r *http.Request) {
hostname, err := utils.PostPara(r, "hostname")
if err != nil {
utils.SendErrorResponse(w, "hostname cannot be empty")
return
}
portString, err := utils.PostPara(r, "port")
if err != nil {
utils.SendErrorResponse(w, "port must be a valid integer")
return
}
port, err := strconv.Atoi(portString)
if err != nil {
utils.SendErrorResponse(w, "port must be a valid integer")
return
}
username, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "username cannot be empty")
return
}
password, err := utils.PostPara(r, "password")
if err != nil {
//Empty password. Use old one if exists
oldConfig := loadSMTPConfig()
if oldConfig.Password == "" {
utils.SendErrorResponse(w, "password cannot be empty")
return
} else {
password = oldConfig.Password
}
}
senderAddr, err := utils.PostPara(r, "senderAddr")
if err != nil {
utils.SendErrorResponse(w, "senderAddr cannot be empty")
return
}
adminAddr, err := utils.PostPara(r, "adminAddr")
if err != nil {
utils.SendErrorResponse(w, "adminAddr cannot be empty")
return
}
//Set the email sender properties
thisEmailSender := email.Sender{
Hostname: strings.TrimSpace(hostname),
Port: port,
Username: strings.TrimSpace(username),
Password: strings.TrimSpace(password),
SenderAddr: strings.TrimSpace(senderAddr),
}
//Write this into database
setSMTPConfig(&thisEmailSender)
//Update the current EmailSender
EmailSender = &thisEmailSender
//Set the admin address of password reset
setSMTPAdminAddress(adminAddr)
//Reply ok
utils.SendOK(w)
}
func HandleSMTPGet(w http.ResponseWriter, r *http.Request) {
// Create a buffer to store the encoded value
var buf bytes.Buffer
// Encode the original object into the buffer
encoder := gob.NewEncoder(&buf)
err := encoder.Encode(*EmailSender)
if err != nil {
utils.SendErrorResponse(w, "Internal encode error")
return
}
// Decode the buffer into a new object
var copied email.Sender
decoder := gob.NewDecoder(&buf)
err = decoder.Decode(&copied)
if err != nil {
utils.SendErrorResponse(w, "Internal decode error")
return
}
copied.Password = ""
js, _ := json.Marshal(copied)
utils.SendJSONResponse(w, string(js))
}
func HandleAdminEmailGet(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(loadSMTPAdminAddr())
utils.SendJSONResponse(w, string(js))
}
func HandleTestEmailSend(w http.ResponseWriter, r *http.Request) {
adminEmailAccount := loadSMTPAdminAddr()
if adminEmailAccount == "" {
utils.SendErrorResponse(w, "Management account not set")
return
}
err := EmailSender.SendEmail(adminEmailAccount,
"SMTP Testing Email | Zoraxy", "This is a test email sent by Zoraxy. Please do not reply to this email.
Zoraxy からのテストメールです。このメールには返信しないでください。
這是由 Zoraxy 發送的測試電子郵件。請勿回覆此郵件。
Ceci est un email de test envoyé par Zoraxy. Merci de ne pas répondre à cet email.
Dies ist eine Test-E-Mail, die von Zoraxy gesendet wurde. Bitte antworten Sie nicht auf diese E-Mail.")
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
/*
SMTP config
The following handle SMTP configs
*/
func setSMTPConfig(config *email.Sender) error {
return sysdb.Write("smtp", "config", config)
}
func loadSMTPConfig() *email.Sender {
if sysdb.KeyExists("smtp", "config") {
thisEmailSender := email.Sender{
Port: 587,
}
err := sysdb.Read("smtp", "config", &thisEmailSender)
if err != nil {
return &email.Sender{
Port: 587,
}
}
return &thisEmailSender
} else {
//Not set. Return an empty one
return &email.Sender{
Port: 587,
}
}
}
func setSMTPAdminAddress(adminAddr string) error {
return sysdb.Write("smtp", "admin", adminAddr)
}
// Load SMTP admin address. Return empty string if not set
func loadSMTPAdminAddr() string {
adminAddr := ""
if sysdb.KeyExists("smtp", "admin") {
err := sysdb.Read("smtp", "admin", &adminAddr)
if err != nil {
return ""
}
return adminAddr
} else {
return ""
}
}
/*
Admin Account Reset
*/
var (
accountResetEmailDelay int64 = 30 //Delay between each account reset email, default 30s
tokenValidDuration int64 = 15 * 60 //Duration of the token, default 15 minutes
lastAccountResetEmail int64 = 0 //Timestamp for last sent account reset email
passwordResetAccessToken string = "" //Access token for resetting password
)
func HandleAdminAccountResetEmail(w http.ResponseWriter, r *http.Request) {
if EmailSender.Username == "" {
//Reset account not setup
utils.SendErrorResponse(w, "Reset account not setup.")
return
}
if loadSMTPAdminAddr() == "" {
utils.SendErrorResponse(w, "Reset account not setup.")
}
//Check if the delay expired
if lastAccountResetEmail+accountResetEmailDelay > time.Now().Unix() {
//Too frequent
utils.SendErrorResponse(w, "You cannot send another account reset email in cooldown time")
return
}
passwordResetAccessToken = uuid.New().String()
//SMTP info exists. Send reset account email
lastAccountResetEmail = time.Now().Unix()
EmailSender.SendEmail(loadSMTPAdminAddr(), "Management Account Reset | Zoraxy",
"Enter the following reset token to reset your password on your Zoraxy router.
"+passwordResetAccessToken+"
This is an automated generated email. DO NOT REPLY TO THIS EMAIL.")
utils.SendOK(w)
}
func HandleNewPasswordSetup(w http.ResponseWriter, r *http.Request) {
if passwordResetAccessToken == "" {
//Not initiated
utils.SendErrorResponse(w, "Invalid usage")
return
}
username, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "Invalid username given")
return
}
token, err := utils.PostPara(r, "token")
if err != nil {
utils.SendErrorResponse(w, "Invalid token given")
return
}
newPassword, err := utils.PostPara(r, "newpw")
if err != nil {
utils.SendErrorResponse(w, "Invalid new password given")
return
}
token = strings.TrimSpace(token)
username = strings.TrimSpace(username)
//Validate the token
if token != passwordResetAccessToken {
utils.SendErrorResponse(w, "Invalid Token")
return
}
//Check if time expired
if lastAccountResetEmail+tokenValidDuration < time.Now().Unix() {
//Expired
utils.SendErrorResponse(w, "Token expired")
return
}
//Check if user exists
if !authAgent.UserExists(username) {
//Invalid admin account name
utils.SendErrorResponse(w, "Invalid Username")
return
}
// Un register the user account
if err := authAgent.UnregisterUser(username); err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Ok. Set the new password
if err := authAgent.CreateUserAccount(username, newPassword, ""); err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}