|
@@ -29,12 +29,11 @@ import (
|
|
|
"bufio"
|
|
|
"fmt"
|
|
|
template "html/template"
|
|
|
- ioutil "io/ioutil"
|
|
|
"log"
|
|
|
"net/http"
|
|
|
- "os"
|
|
|
"strings"
|
|
|
|
|
|
+ auth "../auth"
|
|
|
common "../common"
|
|
|
config "../config"
|
|
|
utils "../utils"
|
|
@@ -55,6 +54,10 @@ const (
|
|
|
AllHeaderMask = 15
|
|
|
)
|
|
|
|
|
|
+const (
|
|
|
+ CookieSessionToken = "gostfix_session"
|
|
|
+)
|
|
|
+
|
|
|
func NewEmail() *common.Mail {
|
|
|
return &common.Mail{
|
|
|
Header: &common.MailHeader{},
|
|
@@ -65,18 +68,18 @@ func NewEmail() *common.Mail {
|
|
|
}
|
|
|
|
|
|
type Server struct {
|
|
|
- mailMaps map[string]string //TODO: Temporary
|
|
|
- fileServer http.Handler
|
|
|
- templater *Templater
|
|
|
- sessionStore *sessions.CookieStore
|
|
|
+ authenticator *auth.Authenticator
|
|
|
+ fileServer http.Handler
|
|
|
+ templater *Templater
|
|
|
+ sessionStore *sessions.CookieStore
|
|
|
}
|
|
|
|
|
|
func NewServer() *Server {
|
|
|
return &Server{
|
|
|
- mailMaps: readMailMaps(), //TODO: Temporary
|
|
|
- templater: NewTemplater("./data/templates"),
|
|
|
- fileServer: http.FileServer(http.Dir("./data")),
|
|
|
- sessionStore: sessions.NewCookieStore(make([]byte, 32)),
|
|
|
+ authenticator: auth.NewAuthenticator(),
|
|
|
+ templater: NewTemplater("./data/templates"),
|
|
|
+ fileServer: http.FileServer(http.Dir("./data")),
|
|
|
+ sessionStore: sessions.NewCookieStore(make([]byte, 32)),
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -91,233 +94,224 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
utils.StartsWith(r.URL.Path, "/assets/") ||
|
|
|
utils.StartsWith(r.URL.Path, "/js/") {
|
|
|
s.fileServer.ServeHTTP(w, r)
|
|
|
- } else if r.URL.Path == "/login" {
|
|
|
- session, _ := s.sessionStore.Get(r, "token")
|
|
|
- user, ok := session.Values["user"].(string)
|
|
|
- if !ok || user == "" {
|
|
|
- user = ""
|
|
|
- if err := r.ParseForm(); err == nil {
|
|
|
- user = r.FormValue("user")
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ switch r.URL.Path {
|
|
|
+ case "/login":
|
|
|
+ s.handleLogin(w, r)
|
|
|
+ case "/logout":
|
|
|
+ s.handleLogout(w, r)
|
|
|
+ case "/messageDetails":
|
|
|
+ s.handleMessageDetails(w, r)
|
|
|
+ default:
|
|
|
+ s.handleMailbox(w, r)
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- _, ok = s.mailMaps[user]
|
|
|
- if utils.RegExpUtilsInstance().EmailChecker.MatchString(user) &&
|
|
|
- user != "" && ok {
|
|
|
- session.Values["user"] = user
|
|
|
- session.Save(r, w)
|
|
|
- http.Redirect(w, r, "/mailbox", http.StatusTemporaryRedirect)
|
|
|
+func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if err := r.ParseForm(); err == nil {
|
|
|
+ user := r.FormValue("user")
|
|
|
+ password := r.FormValue("password")
|
|
|
+ token, ok := s.authenticator.Authenticate(user, password)
|
|
|
+ if ok {
|
|
|
+ s.Login(user, token, w, r)
|
|
|
return
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- session.Values["user"] = ""
|
|
|
- session.Save(r, w)
|
|
|
-
|
|
|
- fmt.Printf("Actual user: %s, Actual map: %v\n", user, s.mailMaps)
|
|
|
- data, _ := ioutil.ReadFile("./data/templates/login.html")
|
|
|
- w.Write(data)
|
|
|
- } else if r.URL.Path == "/logout" {
|
|
|
- fmt.Printf("logout")
|
|
|
- session, _ := s.sessionStore.Get(r, "token")
|
|
|
- session.Values["user"] = ""
|
|
|
- session.Save(r, w)
|
|
|
- fmt.Printf("Reset user session: %s\n", session.Values["user"])
|
|
|
- http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
|
- } else if r.URL.Path == "/messageDetails" {
|
|
|
- fmt.Fprint(w, s.templater.ExecuteDetails(""))
|
|
|
- } else {
|
|
|
- session, _ := s.sessionStore.Get(r, "token")
|
|
|
- user, ok := session.Values["user"].(string)
|
|
|
+ if s.authenticator.Verify(s.extractAuth(w, r)) {
|
|
|
+ http.Redirect(w, r, "/mailbox", http.StatusTemporaryRedirect)
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- fmt.Printf("User session: %s\n", user)
|
|
|
+ s.Logout(w, r)
|
|
|
+ fmt.Fprint(w, s.templater.ExecuteLogin(&LoginTemplateData{
|
|
|
+ common.Version,
|
|
|
+ }))
|
|
|
+}
|
|
|
|
|
|
- if !ok || utils.RegExpUtilsInstance().EmailChecker.FindString(user) != user || user == "" {
|
|
|
- fmt.Print("Invalid user")
|
|
|
- http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
|
- return
|
|
|
- }
|
|
|
+func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
|
|
|
+ s.Logout(w, r)
|
|
|
+ http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
|
+}
|
|
|
|
|
|
- mailRelPath, ok := s.mailMaps[user]
|
|
|
- if !ok {
|
|
|
- fmt.Print("Invalid user")
|
|
|
- http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
|
- return
|
|
|
- }
|
|
|
+func (s *Server) handleMessageDetails(w http.ResponseWriter, r *http.Request) {
|
|
|
+ //TODO: Not implemented yet. Need database mail storage implemented first
|
|
|
+ fmt.Fprint(w, s.templater.ExecuteDetails(""))
|
|
|
+}
|
|
|
|
|
|
- mailPath := config.ConfigInstance().VMailboxBase + "/" + mailRelPath
|
|
|
- if !utils.FileExists(mailPath) {
|
|
|
- fmt.Printf("logout")
|
|
|
- session, _ := s.sessionStore.Get(r, "token")
|
|
|
- session.Values["user"] = ""
|
|
|
- session.Save(r, w)
|
|
|
- fmt.Printf("Reset user session: %s\n", session.Values["user"])
|
|
|
-
|
|
|
- w.WriteHeader(http.StatusInternalServerError)
|
|
|
- fmt.Fprint(w, s.templater.ExecuteError(&Error{
|
|
|
- Code: http.StatusInternalServerError,
|
|
|
- String: "Unable to access your mailbox. Please contact Administrator.",
|
|
|
- }))
|
|
|
- return
|
|
|
- }
|
|
|
+func (s *Server) handleMailbox(w http.ResponseWriter, r *http.Request) {
|
|
|
+ user, token := s.extractAuth(w, r)
|
|
|
+ if !s.authenticator.Verify(user, token) {
|
|
|
+ s.Logout(w, r)
|
|
|
+ http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
|
|
+ }
|
|
|
|
|
|
- file, err := utils.OpenAndLockWait(mailPath)
|
|
|
- if err != nil {
|
|
|
- fmt.Printf("logout")
|
|
|
- session, _ := s.sessionStore.Get(r, "token")
|
|
|
- session.Values["user"] = ""
|
|
|
- session.Save(r, w)
|
|
|
- fmt.Printf("Reset user session: %s\n", session.Values["user"])
|
|
|
-
|
|
|
- w.WriteHeader(http.StatusInternalServerError)
|
|
|
- fmt.Fprint(w, s.templater.ExecuteError(&Error{
|
|
|
- Code: http.StatusInternalServerError,
|
|
|
- String: "Unable to access your mailbox. Please contact Administrator.",
|
|
|
- }))
|
|
|
- return
|
|
|
- }
|
|
|
+ mailPath := config.ConfigInstance().VMailboxBase + "/" + s.authenticator.MailPath(user)
|
|
|
+ if !utils.FileExists(mailPath) {
|
|
|
+ s.Logout(w, r)
|
|
|
+ s.Error(http.StatusInternalServerError, "Unable to access your mailbox. Please contact Administrator.", w, r)
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- defer file.CloseAndUnlock()
|
|
|
-
|
|
|
- scanner := bufio.NewScanner(file)
|
|
|
- activeBoundary := ""
|
|
|
- var previousHeader *string = nil
|
|
|
- var emails []*common.Mail
|
|
|
- mandatoryHeaders := 0
|
|
|
- email := NewEmail()
|
|
|
- state := StateHeaderScan
|
|
|
- for scanner.Scan() {
|
|
|
- if scanner.Text() == "" {
|
|
|
- if state == StateHeaderScan && mandatoryHeaders&AtLeastOneHeaderMask == AtLeastOneHeaderMask {
|
|
|
- boundaryCapture := utils.RegExpUtilsInstance().BoundaryFinder.FindStringSubmatch(email.Body.ContentType)
|
|
|
- if len(boundaryCapture) == 2 {
|
|
|
- activeBoundary = boundaryCapture[1]
|
|
|
- } else {
|
|
|
- activeBoundary = ""
|
|
|
- }
|
|
|
- state = StateBodyScan
|
|
|
- // fmt.Printf("--------------------------Start body scan content type:%s boundary: %s -------------------------\n", email.Body.ContentType, activeBoundary)
|
|
|
- } else if state == StateBodyScan {
|
|
|
- // fmt.Printf("--------------------------Previous email-------------------------\n%v\n", email)
|
|
|
- if activeBoundary == "" {
|
|
|
- previousHeader = nil
|
|
|
- activeBoundary = ""
|
|
|
- // fmt.Printf("Actual headers: %d\n", mandatoryHeaders)
|
|
|
- if mandatoryHeaders == AllHeaderMask {
|
|
|
- emails = append(emails, email)
|
|
|
- }
|
|
|
- email = NewEmail()
|
|
|
- state = StateHeaderScan
|
|
|
- mandatoryHeaders = 0
|
|
|
- } else {
|
|
|
- // fmt.Printf("Still in body scan\n")
|
|
|
- continue
|
|
|
- }
|
|
|
+ file, err := utils.OpenAndLockWait(mailPath)
|
|
|
+ if err != nil {
|
|
|
+ s.Logout(w, r)
|
|
|
+ s.Error(http.StatusInternalServerError, "Unable to access your mailbox. Please contact Administrator.", w, r)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ defer file.CloseAndUnlock()
|
|
|
+
|
|
|
+ scanner := bufio.NewScanner(file)
|
|
|
+ activeBoundary := ""
|
|
|
+ var previousHeader *string = nil
|
|
|
+ var emails []*common.Mail
|
|
|
+ mandatoryHeaders := 0
|
|
|
+ email := NewEmail()
|
|
|
+ state := StateHeaderScan
|
|
|
+ for scanner.Scan() {
|
|
|
+ if scanner.Text() == "" {
|
|
|
+ if state == StateHeaderScan && mandatoryHeaders&AtLeastOneHeaderMask == AtLeastOneHeaderMask {
|
|
|
+ boundaryCapture := utils.RegExpUtilsInstance().BoundaryFinder.FindStringSubmatch(email.Body.ContentType)
|
|
|
+ if len(boundaryCapture) == 2 {
|
|
|
+ activeBoundary = boundaryCapture[1]
|
|
|
} else {
|
|
|
- // fmt.Printf("Empty line in state %d\n", state)
|
|
|
+ activeBoundary = ""
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- if state == StateHeaderScan {
|
|
|
- capture := utils.RegExpUtilsInstance().HeaderFinder.FindStringSubmatch(scanner.Text())
|
|
|
- if len(capture) == 3 {
|
|
|
- // fmt.Printf("capture Header %s : %s\n", strings.ToLower(capture[0]), strings.ToLower(capture[1]))
|
|
|
- header := strings.ToLower(capture[1])
|
|
|
- mandatoryHeaders |= AtLeastOneHeaderMask
|
|
|
- switch header {
|
|
|
- case "from":
|
|
|
- previousHeader = &email.Header.From
|
|
|
- mandatoryHeaders |= FromHeaderMask
|
|
|
- case "to":
|
|
|
- previousHeader = &email.Header.To
|
|
|
- mandatoryHeaders |= ToHeaderMask
|
|
|
- case "cc":
|
|
|
- previousHeader = &email.Header.Cc
|
|
|
- case "bcc":
|
|
|
- previousHeader = &email.Header.Bcc
|
|
|
- mandatoryHeaders |= ToHeaderMask
|
|
|
- case "subject":
|
|
|
- previousHeader = &email.Header.Subject
|
|
|
- case "date":
|
|
|
- previousHeader = &email.Header.Date
|
|
|
- mandatoryHeaders |= DateHeaderMask
|
|
|
- case "content-type":
|
|
|
- previousHeader = &email.Body.ContentType
|
|
|
- default:
|
|
|
- previousHeader = nil
|
|
|
- }
|
|
|
- if previousHeader != nil {
|
|
|
- *previousHeader += capture[2]
|
|
|
+ state = StateBodyScan
|
|
|
+ // fmt.Printf("--------------------------Start body scan content type:%s boundary: %s -------------------------\n", email.Body.ContentType, activeBoundary)
|
|
|
+ } else if state == StateBodyScan {
|
|
|
+ // fmt.Printf("--------------------------Previous email-------------------------\n%v\n", email)
|
|
|
+ if activeBoundary == "" {
|
|
|
+ previousHeader = nil
|
|
|
+ activeBoundary = ""
|
|
|
+ // fmt.Printf("Actual headers: %d\n", mandatoryHeaders)
|
|
|
+ if mandatoryHeaders == AllHeaderMask {
|
|
|
+ emails = append(emails, email)
|
|
|
}
|
|
|
+ email = NewEmail()
|
|
|
+ state = StateHeaderScan
|
|
|
+ mandatoryHeaders = 0
|
|
|
+ } else {
|
|
|
+ // fmt.Printf("Still in body scan\n")
|
|
|
continue
|
|
|
}
|
|
|
+ } else {
|
|
|
+ // fmt.Printf("Empty line in state %d\n", state)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- capture = utils.RegExpUtilsInstance().FoldingFinder.FindStringSubmatch(scanner.Text())
|
|
|
- if len(capture) == 2 && previousHeader != nil {
|
|
|
- *previousHeader += capture[1]
|
|
|
- continue
|
|
|
+ if state == StateHeaderScan {
|
|
|
+ capture := utils.RegExpUtilsInstance().HeaderFinder.FindStringSubmatch(scanner.Text())
|
|
|
+ if len(capture) == 3 {
|
|
|
+ // fmt.Printf("capture Header %s : %s\n", strings.ToLower(capture[0]), strings.ToLower(capture[1]))
|
|
|
+ header := strings.ToLower(capture[1])
|
|
|
+ mandatoryHeaders |= AtLeastOneHeaderMask
|
|
|
+ switch header {
|
|
|
+ case "from":
|
|
|
+ previousHeader = &email.Header.From
|
|
|
+ mandatoryHeaders |= FromHeaderMask
|
|
|
+ case "to":
|
|
|
+ previousHeader = &email.Header.To
|
|
|
+ mandatoryHeaders |= ToHeaderMask
|
|
|
+ case "cc":
|
|
|
+ previousHeader = &email.Header.Cc
|
|
|
+ case "bcc":
|
|
|
+ previousHeader = &email.Header.Bcc
|
|
|
+ mandatoryHeaders |= ToHeaderMask
|
|
|
+ case "subject":
|
|
|
+ previousHeader = &email.Header.Subject
|
|
|
+ case "date":
|
|
|
+ previousHeader = &email.Header.Date
|
|
|
+ mandatoryHeaders |= DateHeaderMask
|
|
|
+ case "content-type":
|
|
|
+ previousHeader = &email.Body.ContentType
|
|
|
+ default:
|
|
|
+ previousHeader = nil
|
|
|
}
|
|
|
- } else {
|
|
|
- // email.Body.Content += scanner.Text() + "\n"
|
|
|
- if activeBoundary != "" {
|
|
|
- capture := utils.RegExpUtilsInstance().BoundaryEndFinder.FindStringSubmatch(scanner.Text())
|
|
|
- if len(capture) == 2 {
|
|
|
- // fmt.Printf("capture Boundary End %s\n", capture[1])
|
|
|
- if activeBoundary == capture[1] {
|
|
|
- state = StateBodyScan
|
|
|
- activeBoundary = ""
|
|
|
- }
|
|
|
-
|
|
|
- continue
|
|
|
- }
|
|
|
- // capture = boundaryStartFinder.FindStringSubmatch(scanner.Text())
|
|
|
- // if len(capture) == 2 && activeBoundary == capture[1] {
|
|
|
- // // fmt.Printf("capture Boundary Start %s\n", capture[1])
|
|
|
- // state = StateContentScan
|
|
|
- // continue
|
|
|
- // }
|
|
|
+ if previousHeader != nil {
|
|
|
+ *previousHeader += capture[2]
|
|
|
}
|
|
|
+ continue
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if state == StateBodyScan && mandatoryHeaders == AllHeaderMask { //Finalize if body read till EOF
|
|
|
- // fmt.Printf("--------------------------Previous email-------------------------\n%v\n", email)
|
|
|
+ capture = utils.RegExpUtilsInstance().FoldingFinder.FindStringSubmatch(scanner.Text())
|
|
|
+ if len(capture) == 2 && previousHeader != nil {
|
|
|
+ *previousHeader += capture[1]
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // email.Body.Content += scanner.Text() + "\n"
|
|
|
+ if activeBoundary != "" {
|
|
|
+ capture := utils.RegExpUtilsInstance().BoundaryEndFinder.FindStringSubmatch(scanner.Text())
|
|
|
+ if len(capture) == 2 {
|
|
|
+ // fmt.Printf("capture Boundary End %s\n", capture[1])
|
|
|
+ if activeBoundary == capture[1] {
|
|
|
+ state = StateBodyScan
|
|
|
+ activeBoundary = ""
|
|
|
+ }
|
|
|
|
|
|
- previousHeader = nil
|
|
|
- activeBoundary = ""
|
|
|
- emails = append(emails, email)
|
|
|
- state = StateHeaderScan
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ // capture = boundaryStartFinder.FindStringSubmatch(scanner.Text())
|
|
|
+ // if len(capture) == 2 && activeBoundary == capture[1] {
|
|
|
+ // // fmt.Printf("capture Boundary Start %s\n", capture[1])
|
|
|
+ // state = StateContentScan
|
|
|
+ // continue
|
|
|
+ // }
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ if state == StateBodyScan && mandatoryHeaders == AllHeaderMask { //Finalize if body read till EOF
|
|
|
+ // fmt.Printf("--------------------------Previous email-------------------------\n%v\n", email)
|
|
|
|
|
|
- fmt.Fprint(w, s.templater.ExecuteIndex(&Index{
|
|
|
- MailList: template.HTML(s.templater.ExecuteMailList(emails)),
|
|
|
- Folders: "Folders",
|
|
|
- Version: common.Version,
|
|
|
- }))
|
|
|
+ previousHeader = nil
|
|
|
+ activeBoundary = ""
|
|
|
+ emails = append(emails, email)
|
|
|
+ state = StateHeaderScan
|
|
|
}
|
|
|
+
|
|
|
+ fmt.Fprint(w, s.templater.ExecuteIndex(&IndexTemplateData{
|
|
|
+ MailList: template.HTML(s.templater.ExecuteMailList(emails)),
|
|
|
+ Folders: "Folders",
|
|
|
+ Version: common.Version,
|
|
|
+ }))
|
|
|
}
|
|
|
|
|
|
-func readMailMaps() map[string]string { //TODO: Temporary
|
|
|
- mailMaps := make(map[string]string)
|
|
|
- mapsFile := config.ConfigInstance().VMailboxMaps
|
|
|
- if !utils.FileExists(mapsFile) {
|
|
|
- return mailMaps
|
|
|
- }
|
|
|
+func (s *Server) Logout(w http.ResponseWriter, r *http.Request) {
|
|
|
+ fmt.Println("logout")
|
|
|
|
|
|
- file, err := os.Open(mapsFile)
|
|
|
- if err != nil {
|
|
|
- log.Fatalf("Unable to open virtual mailbox maps %s\n", mapsFile)
|
|
|
- }
|
|
|
+ session, _ := s.sessionStore.Get(r, CookieSessionToken)
|
|
|
+ session.Values["user"] = ""
|
|
|
+ session.Values["token"] = ""
|
|
|
+ session.Save(r, w)
|
|
|
+}
|
|
|
|
|
|
- scanner := bufio.NewScanner(file)
|
|
|
+func (s *Server) Login(user, token string, w http.ResponseWriter, r *http.Request) {
|
|
|
+ session, _ := s.sessionStore.Get(r, CookieSessionToken)
|
|
|
+ session.Values["user"] = user
|
|
|
+ session.Values["token"] = token
|
|
|
+ session.Save(r, w)
|
|
|
+ http.Redirect(w, r, "/mailbox", http.StatusTemporaryRedirect)
|
|
|
+}
|
|
|
|
|
|
- for scanner.Scan() {
|
|
|
- mailPathPair := strings.Split(scanner.Text(), " ")
|
|
|
- if len(mailPathPair) != 2 {
|
|
|
- log.Printf("Invalid record in virtual mailbox maps %s", scanner.Text())
|
|
|
- continue
|
|
|
- }
|
|
|
- mailMaps[mailPathPair[0]] = mailPathPair[1]
|
|
|
+func (s *Server) Error(code int, text string, w http.ResponseWriter, r *http.Request) {
|
|
|
+ w.WriteHeader(http.StatusInternalServerError)
|
|
|
+ fmt.Fprint(w, s.templater.ExecuteError(&ErrorTemplateData{
|
|
|
+ Code: code,
|
|
|
+ Text: "Unable to access your mailbox. Please contact Administrator.",
|
|
|
+ }))
|
|
|
+}
|
|
|
+
|
|
|
+func (s *Server) extractAuth(w http.ResponseWriter, r *http.Request) (user, token string) {
|
|
|
+ session, err := s.sessionStore.Get(r, CookieSessionToken)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("Unable to read user session %s\n", err)
|
|
|
+ return
|
|
|
}
|
|
|
+ user, _ = session.Values["user"].(string)
|
|
|
+ token, _ = session.Values["token"].(string)
|
|
|
|
|
|
- return mailMaps
|
|
|
+ return
|
|
|
}
|