package main import ( "crypto/sha512" "encoding/base64" "encoding/binary" "io" "log" "net/http" "os" "os/exec" "time" "gopkg.in/gomail.v2" ) func (l LDAPConn) genToken(dn string, previous bool) string { hour := time.Now() // Generate the previous token? if previous { hour.Add(time.Hour * -1) } b := make([]byte, binary.MaxVarintLen64) binary.PutVarint(b, hour.Round(time.Hour).Unix()) // Search the email address and current password entries, err := l.GetEntry(dn) if err != nil { log.Println("Unable to generate token:", err) return "#err" } email := "" curpasswd := "" for _, e := range entries { if e.Name == "mail" { email += e.Values[0] } else if e.Name == "userPassword" { curpasswd += e.Values[0] } } // Hash that hash := sha512.New() hash.Write(b) hash.Write([]byte(dn)) hash.Write([]byte(email)) hash.Write([]byte(curpasswd)) return base64.StdEncoding.EncodeToString(hash.Sum(nil)[:]) } func lostPassword(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { displayTmpl(w, "lost.html", map[string]interface{}{}) return } // Connect to the LDAP server conn, err := myLDAP.Connect() if err != nil || conn == nil { log.Println(err) displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()}) return } // Bind as service to perform the search err = conn.ServiceBind() if err != nil { log.Println(err) displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()}) return } // Search the dn of the given user dn, err := conn.SearchDN(r.PostFormValue("login"), true) if err != nil { log.Println(err) displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()}) return } // Generate the token token := conn.genToken(dn, false) // Search the email address entries, err := conn.GetEntry(dn) if err != nil { log.Println(err) displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": err.Error()}) return } email := "" cn := "" for _, e := range entries { if e.Name == "mail" { email = e.Values[0] } if e.Name == "cn" { cn = e.Values[0] } } if email == "" { log.Println("Unable to find a valid adress for user " + dn) displayTmplError(w, http.StatusBadRequest, "lost.html", map[string]interface{}{"error": "We were unable to find a valid email address associated with your account. Please contact an administrator."}) return } // Send the email m := gomail.NewMessage() m.SetHeader("From", "noreply@nemunai.re") m.SetHeader("To", email) m.SetHeader("Subject", "SSO nemunai.re: password recovery") m.SetBody("text/plain", "Hello "+cn+"!\n\nSomeone, and we hope it's you, requested to reset your account password. \nIn order to continue, go to:\nhttps://ldap.nemunai.re/reset?l="+r.PostFormValue("login")+"&t="+token+"\n\nBest regards,\n-- \nnemunai.re SSO") var s gomail.Sender if myLDAP.MailHost != "" { d := gomail.NewDialer(myLDAP.MailHost, myLDAP.MailPort, myLDAP.MailUser, myLDAP.MailPassword) s, err = d.Dial() if err != nil { log.Println("Unable to connect to email server: " + err.Error()) displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": "Unable to connect to email server: " + err.Error()}) return } } else { // Using local sendmail: delegate to the local admin sys the responsability to transport the mail s = gomail.SendFunc(func(from string, to []string, msg io.WriterTo) error { cmd := exec.Command("sendmail", "-t") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr pw, err := cmd.StdinPipe() if err != nil { return err } err = cmd.Start() if err != nil { return err } var errs [3]error _, errs[0] = m.WriteTo(pw) errs[1] = pw.Close() errs[2] = cmd.Wait() for _, err = range errs { if err != nil { return err } } return nil }) } if err := gomail.Send(s, m); err != nil { log.Println("Unable to send email: " + err.Error()) displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]interface{}{"error": "Unable to send email: " + err.Error()}) return } displayMsg(w, "Password recovery email sent, check your inbox.", http.StatusOK) }