2024-02-19 16:47:27 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-05-07 11:54:41 +00:00
|
|
|
"crypto"
|
2024-05-07 12:11:11 +00:00
|
|
|
"crypto/rand"
|
2024-05-07 12:00:34 +00:00
|
|
|
"encoding/base64"
|
2024-05-07 11:54:41 +00:00
|
|
|
"fmt"
|
2024-05-07 12:11:11 +00:00
|
|
|
"io"
|
2024-02-19 16:47:27 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
2024-05-07 11:54:41 +00:00
|
|
|
"strings"
|
2024-02-19 16:47:27 +00:00
|
|
|
"text/template"
|
|
|
|
"time"
|
|
|
|
|
2024-02-19 17:08:07 +00:00
|
|
|
"github.com/gorilla/mux"
|
|
|
|
|
2024-02-19 16:47:27 +00:00
|
|
|
"git.teamworkapps.com/shortcut/forms"
|
|
|
|
"github.com/knadh/go-pop3"
|
|
|
|
)
|
|
|
|
|
|
|
|
// We use my forms package to create a password change form which on success executes a docker call to change the password
|
|
|
|
|
|
|
|
type tplData struct {
|
|
|
|
Time int64
|
|
|
|
Body string
|
|
|
|
}
|
|
|
|
|
2024-02-19 17:08:07 +00:00
|
|
|
func mwLog(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
log.Printf("%s %s", r.RemoteAddr, r.URL.String())
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-19 16:47:27 +00:00
|
|
|
func main() {
|
|
|
|
|
|
|
|
forms.GlobalStyles.ContainerClasses = "w-full bg-gray-100 justify-center py-6 rounded-lg shadow flex"
|
|
|
|
forms.GlobalStyles.ItemClasses = "w-full px-1 py-2"
|
|
|
|
forms.GlobalStyles.ErrorClasses = "text-red-600 text-right font-bold text-xl"
|
|
|
|
forms.GlobalStyles.LabelClasses = "block text-sm font-medium leading-6 text-gray-900"
|
|
|
|
forms.GlobalStyles.InputClasses = "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
|
|
|
f := forms.NewForm()
|
|
|
|
e := forms.NewElement()
|
|
|
|
e.Label = "Email Address"
|
|
|
|
e.Type = "text"
|
|
|
|
e.Validator = "email"
|
2024-02-20 16:52:37 +00:00
|
|
|
e.FailMessage = "Must be a valid email address"
|
2024-02-19 16:47:27 +00:00
|
|
|
e.Name = "email"
|
|
|
|
f.Add(e)
|
|
|
|
|
|
|
|
e = forms.NewElement()
|
|
|
|
e.Label = "Old Password"
|
|
|
|
e.Type = "password"
|
|
|
|
e.Validator = ""
|
2024-02-20 16:52:37 +00:00
|
|
|
e.FailMessage = "Passsword Incorrect"
|
2024-02-19 16:47:27 +00:00
|
|
|
e.Name = "oldpassword"
|
|
|
|
f.Add(e)
|
|
|
|
|
|
|
|
e = forms.NewElement()
|
|
|
|
e.Label = "New Password"
|
|
|
|
e.Type = "password"
|
2024-02-20 16:52:37 +00:00
|
|
|
e.FailMessage = "Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter and one digit"
|
2024-02-19 16:47:27 +00:00
|
|
|
e.Validator = "minlength=8;haslowercase;hasuppercase;hasdigit"
|
|
|
|
e.Name = "newpassword"
|
|
|
|
f.Add(e)
|
|
|
|
|
|
|
|
e = forms.NewElement()
|
|
|
|
e.Label = "Confirm Password"
|
|
|
|
e.Type = "password"
|
2024-02-20 16:52:37 +00:00
|
|
|
e.FailMessage = "Passwords do not match"
|
2024-02-19 16:47:27 +00:00
|
|
|
e.Validator = "mustmatch=newpassword"
|
|
|
|
e.Name = "confirmpassword"
|
|
|
|
f.Add(e)
|
|
|
|
|
|
|
|
e = forms.NewElement()
|
|
|
|
e.Label = "Change Password"
|
|
|
|
e.Type = "submit"
|
|
|
|
e.InputClasses = "flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
|
|
|
f.Add(e)
|
|
|
|
|
2024-02-19 17:15:51 +00:00
|
|
|
f.Route = "/chpass/changepassword"
|
2024-02-19 16:47:27 +00:00
|
|
|
out, err := f.ToJSON()
|
|
|
|
if err == nil {
|
|
|
|
os.WriteFile("form.json", out, 0644)
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpFile := "template.html"
|
|
|
|
tmpl, err := template.New(path.Base(tmpFile)).ParseFiles(tmpFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
var d tplData
|
|
|
|
|
2024-02-19 17:08:07 +00:00
|
|
|
r := mux.NewRouter()
|
|
|
|
r.Use(mwLog)
|
|
|
|
|
|
|
|
r.HandleFunc("/style.css", http.FileServer(http.Dir("./")).ServeHTTP)
|
|
|
|
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
2024-02-19 17:12:31 +00:00
|
|
|
d.Body = f.Render(false)
|
2024-02-19 16:47:27 +00:00
|
|
|
tmpl.Execute(w, d)
|
|
|
|
})
|
2024-02-19 17:08:07 +00:00
|
|
|
r.HandleFunc("/changepassword", func(w http.ResponseWriter, r *http.Request) {
|
2024-02-19 16:47:27 +00:00
|
|
|
if f.Validate(r) != nil {
|
|
|
|
d.Body = f.Render(true)
|
|
|
|
tmpl.Execute(w, d)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Test if the given username ans password can authenticate via pop3
|
|
|
|
// If so, change the password
|
|
|
|
|
2024-02-19 19:35:01 +00:00
|
|
|
p := pop3.New(pop3.Opt{Host: "teamworkapps.com", Port: 995, TLSEnabled: true})
|
2024-02-19 16:47:27 +00:00
|
|
|
|
|
|
|
c, err := p.NewConn()
|
|
|
|
if err != nil {
|
2024-02-19 17:18:08 +00:00
|
|
|
log.Println(err)
|
2024-02-20 16:52:37 +00:00
|
|
|
d.Body = "Internal error"
|
2024-02-19 16:47:27 +00:00
|
|
|
tmpl.Execute(w, d)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer c.Quit()
|
|
|
|
|
|
|
|
// Authenticate.
|
|
|
|
if err := c.Auth(f.GetValue("email"), f.GetValue("oldpassword")); err != nil {
|
|
|
|
f.MakeInvalid("oldpassword", "Invalid password")
|
|
|
|
d.Body = f.Render(true)
|
|
|
|
tmpl.Execute(w, d)
|
|
|
|
return
|
|
|
|
}
|
2024-05-07 11:54:41 +00:00
|
|
|
// Change the password
|
|
|
|
if !modifyPasswordFile(f.GetValue("email"), f.GetValue("newpassword")) {
|
2024-02-20 16:52:37 +00:00
|
|
|
log.Println(err)
|
|
|
|
d.Body = "Internal error"
|
|
|
|
tmpl.Execute(w, d)
|
|
|
|
return
|
|
|
|
}
|
2024-02-20 17:16:36 +00:00
|
|
|
d.Body = "Password changed. You will need to login again."
|
2024-02-20 16:52:37 +00:00
|
|
|
tmpl.Execute(w, d)
|
2024-02-19 16:47:27 +00:00
|
|
|
})
|
|
|
|
d.Time = time.Now().Unix()
|
|
|
|
d.Body = f.Render(true)
|
|
|
|
tmpl.Execute(os.Stdout, d)
|
2024-02-19 17:14:26 +00:00
|
|
|
http.ListenAndServe("127.0.0.1:8910", r)
|
2024-02-19 16:47:27 +00:00
|
|
|
}
|
2024-05-07 11:54:41 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
example password entry:
|
|
|
|
recreation@pinnaclelake.com|{SHA512-CRYPT}$6$uTiy7Q5xn1CIN22w$3VAElns3TFfejtdTCTJMcz8k2UPPwAmlUIoXC4NgPJRnDcgo3CnI91EsB6irgwuecCrolxAx7i4mjvTxPaAlf0
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
// createPasswordEntry: input email and plain text password, output a password entry with sha512-crypt.
|
|
|
|
|
|
|
|
func modifyPasswordFile(email, password string) bool {
|
|
|
|
// read the password file
|
|
|
|
// find the entry with the given email
|
|
|
|
// replace the password with the new password
|
|
|
|
// write the file back
|
2024-05-07 11:55:22 +00:00
|
|
|
in, err := os.ReadFile("/root/docker/mail/config/postfix-accounts.cf")
|
2024-05-07 11:54:41 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return false
|
|
|
|
}
|
2024-05-07 11:55:22 +00:00
|
|
|
out, err := os.Create("/root/docker/mail/config/postfix-accounts.cf.tmp")
|
2024-05-07 11:54:41 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
lines := strings.Split(string(in), "\n")
|
|
|
|
for _, line := range lines {
|
|
|
|
if strings.HasPrefix(line, email) {
|
|
|
|
line = createPasswordEntry(email, password)
|
|
|
|
}
|
|
|
|
out.WriteString(line)
|
|
|
|
out.WriteString("\n")
|
|
|
|
}
|
|
|
|
out.Close()
|
2024-05-07 12:11:54 +00:00
|
|
|
os.Rename("/root/docker/mail/config/postfix-accounts.cf", "/root/docker/mail/config/postfix-accounts.cf.old")
|
2024-05-07 11:55:22 +00:00
|
|
|
os.Rename("/root/docker/mail/config/postfix-accounts.cf.tmp", "/root/docker/mail/config/postfix-accounts.cf")
|
2024-05-07 11:54:41 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func createPasswordEntry(email, password string) string {
|
|
|
|
c := crypto.SHA512.New()
|
|
|
|
c.Write([]byte(password))
|
2024-05-07 12:11:11 +00:00
|
|
|
// hash := c.Sum(nil)
|
2024-05-07 12:27:03 +00:00
|
|
|
salt := base64.StdEncoding.EncodeToString(getSalt())
|
|
|
|
str := base64.StdEncoding.EncodeToString([]byte(string(c.Sum(nil))))
|
|
|
|
return fmt.Sprintf("%s|{SHA512-CRYPT}$6$%s$%s", email, salt, str)
|
2024-05-07 11:54:41 +00:00
|
|
|
}
|
2024-05-07 12:11:11 +00:00
|
|
|
|
2024-05-07 12:27:03 +00:00
|
|
|
func getSalt() []byte {
|
2024-05-07 12:14:39 +00:00
|
|
|
salt := make([]byte, 5)
|
2024-05-07 12:11:11 +00:00
|
|
|
_, err := io.ReadFull(rand.Reader, salt)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2024-05-07 12:27:03 +00:00
|
|
|
return salt
|
2024-05-07 12:11:11 +00:00
|
|
|
}
|