diff --git a/addy.go b/addy.go index fc12c8f..5866f70 100644 --- a/addy.go +++ b/addy.go @@ -115,7 +115,6 @@ func addyAliasAPI(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - defer conn.Close() err = conn.ServiceBind() if err != nil { @@ -198,7 +197,6 @@ func addyAliasAPIDelete(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - defer conn.Close() err = conn.ServiceBind() if err != nil { diff --git a/change.go b/change.go index cab8b9e..4e33727 100644 --- a/change.go +++ b/change.go @@ -67,51 +67,31 @@ func changePassword(w http.ResponseWriter, r *http.Request) { // Check the two new passwords are identical if r.PostFormValue("newpassword") != r.PostFormValue("new2password") { renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.") - return - } - if len(r.PostFormValue("login")) == 0 { + } else if len(r.PostFormValue("login")) == 0 { renderError(http.StatusNotAcceptable, "Please provide a valid login") - return - } - if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil { + } else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil { renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error()) - return + } else { + conn, err := myLDAP.Connect() + if err != nil || conn == nil { + log.Println(err) + renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") + } else if err := conn.ServiceBind(); err != nil { + log.Println(err) + renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") + } else if dn, err := conn.SearchDN(r.PostFormValue("login"), true); err != nil { + log.Println(err) + // User not found: perform a dummy bind to prevent username enumeration via timing. + conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password")) + renderError(http.StatusUnauthorized, "Invalid login or password.") + } else if err := conn.Bind(dn, r.PostFormValue("password")); err != nil { + log.Println(err) + renderError(http.StatusUnauthorized, "Invalid login or password.") + } else if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil { + log.Println(err) + renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") + } else { + displayMsg(w, "Password successfully changed!", http.StatusOK) + } } - - conn, err := myLDAP.Connect() - if err != nil || conn == nil { - log.Println(err) - renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") - return - } - defer conn.Close() - - if err := conn.ServiceBind(); err != nil { - log.Println(err) - renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") - return - } - - dn, err := conn.SearchDN(r.PostFormValue("login"), true) - if err != nil { - log.Println(err) - // User not found: perform a dummy bind to prevent username enumeration via timing. - conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password")) - renderError(http.StatusUnauthorized, "Invalid login or password.") - return - } - - if err := conn.Bind(dn, r.PostFormValue("password")); err != nil { - log.Println(err) - renderError(http.StatusUnauthorized, "Invalid login or password.") - return - } - - if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil { - log.Println(err) - renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") - return - } - - displayMsg(w, "Password successfully changed!", http.StatusOK) } diff --git a/ldap.go b/ldap.go index c9e6b25..85271fe 100644 --- a/ldap.go +++ b/ldap.go @@ -4,10 +4,8 @@ import ( "crypto/rand" "crypto/tls" "encoding/base64" + "errors" "fmt" - "strconv" - "strings" - "time" "github.com/amoghe/go-crypt" "github.com/go-ldap/ldap/v3" @@ -21,45 +19,38 @@ type LDAP struct { BaseDN string ServiceDN string ServicePassword string -} - -type SMTPConfig struct { - MailHost string - MailPort int - MailUser string - MailPassword string - MailFrom string + MailHost string + MailPort int + MailUser string + MailPassword string + MailFrom string } func (l LDAP) Connect() (*LDAPConn, error) { - addr := fmt.Sprintf("%s:%d", l.Host, l.Port) - - var opts []ldap.DialOpt if l.Ssl { - opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{ServerName: l.Host})) - } - - scheme := "ldap" - if l.Ssl { - scheme = "ldaps" - } - - c, err := ldap.DialURL(fmt.Sprintf("%s://%s", scheme, addr), opts...) - if err != nil { - return nil, fmt.Errorf("unable to establish %s connection to %s: %w", strings.ToUpper(scheme), addr, err) - } - - if l.Starttls { - if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil { - c.Close() - return nil, fmt.Errorf("unable to StartTLS: %w", err) + if c, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port), &tls.Config{ServerName: l.Host}); err != nil { + return nil, errors.New("unable to establish LDAPS connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error()) + } else { + return &LDAPConn{ + LDAP: l, + connection: c, + }, nil + } + } else if c, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port)); err != nil { + return nil, errors.New("unable to establish LDAP connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error()) + } else { + if l.Starttls { + if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil { + c.Close() + return nil, errors.New("unable to StartTLS: " + err.Error()) + } } - } - return &LDAPConn{ - LDAP: l, - connection: c, - }, nil + return &LDAPConn{ + LDAP: l, + connection: c, + }, nil + } } type LDAPConn struct { @@ -67,10 +58,6 @@ type LDAPConn struct { connection *ldap.Conn } -func (l LDAPConn) Close() { - l.connection.Close() -} - func (l LDAPConn) ServiceBind() error { return l.connection.Bind(l.ServiceDN, l.ServicePassword) } @@ -98,11 +85,8 @@ func (l LDAPConn) SearchDN(username string, person bool) (string, error) { return "", err } - if len(sr.Entries) == 0 { - return "", fmt.Errorf("user %q not found", username) - } - if len(sr.Entries) > 1 { - return "", fmt.Errorf("multiple entries (%d) found for user %q", len(sr.Entries), username) + if len(sr.Entries) != 1 { + return "", errors.New("User does not exist or too many entries returned") } return sr.Entries[0].DN, nil @@ -120,11 +104,8 @@ func (l LDAPConn) GetEntry(dn string) ([]*ldap.EntryAttribute, error) { return nil, err } - if len(sr.Entries) == 0 { - return nil, fmt.Errorf("entry not found for DN %q", dn) - } - if len(sr.Entries) > 1 { - return nil, fmt.Errorf("multiple entries (%d) found for DN %q", len(sr.Entries), dn) + if len(sr.Entries) != 1 { + return nil, errors.New("User does not exist or too many entries returned") } return sr.Entries[0].Attributes, nil @@ -152,7 +133,6 @@ func (l LDAPConn) ChangePassword(dn string, rawpassword string) error { modify := ldap.NewModifyRequest(dn, nil) modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd}) - modify.Replace("shadowLastChange", []string{strconv.FormatInt(time.Now().Unix()/86400, 10)}) return l.connection.Modify(modify) } diff --git a/login.go b/login.go index 41b9bc9..60e3f8f 100644 --- a/login.go +++ b/login.go @@ -78,7 +78,6 @@ func login(login string, password string) ([]*ldap.EntryAttribute, error) { if err != nil || conn == nil { return nil, err } - defer conn.Close() if err = conn.ServiceBind(); err != nil { return nil, err diff --git a/lost.go b/lost.go index 889da5a..8fbef23 100644 --- a/lost.go +++ b/lost.go @@ -129,7 +129,6 @@ func lostPassword(w http.ResponseWriter, r *http.Request) { displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]any{"error": "Unable to process your request. Please try again later."}) return } - defer conn.Close() // Generate the token token, dn, err := lostPasswordToken(conn, r.PostFormValue("login")) @@ -167,14 +166,14 @@ func lostPassword(w http.ResponseWriter, r *http.Request) { // Send the email m := gomail.NewMessage() - m.SetHeader("From", mySMTP.MailFrom) + m.SetHeader("From", myLDAP.MailFrom) 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:\n"+myPublicURL+"/reset?l="+r.PostFormValue("login")+"&t="+token+"\n\nThis link expires in 1 hour and can only be used once.\n\nBest regards,\n-- \nnemunai.re SSO") var s gomail.Sender - if mySMTP.MailHost != "" { - d := gomail.NewDialer(mySMTP.MailHost, mySMTP.MailPort, mySMTP.MailUser, mySMTP.MailPassword) + 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()) diff --git a/main.go b/main.go index 1f2020a..5701f36 100644 --- a/main.go +++ b/main.go @@ -31,12 +31,9 @@ var dockerRegistrySecret string var allowedAliasDomains []string var myLDAP = LDAP{ - Host: "localhost", - Port: 389, - BaseDN: "dc=example,dc=com", -} - -var mySMTP = SMTPConfig{ + Host: "localhost", + Port: 389, + BaseDN: "dc=example,dc=com", MailPort: 587, MailFrom: "noreply@example.com", } @@ -118,13 +115,8 @@ func main() { log.Fatal(err) } else if cnt, err := io.ReadAll(fd); err != nil { log.Fatal(err) - } else { - if err := json.Unmarshal(cnt, &myLDAP); err != nil { - log.Fatal(err) - } - if err := json.Unmarshal(cnt, &mySMTP); err != nil { - log.Fatal(err) - } + } else if err := json.Unmarshal(cnt, &myLDAP); err != nil { + log.Fatal(err) } } @@ -164,17 +156,17 @@ func main() { } if val, ok := os.LookupEnv("SMTP_HOST"); ok { - mySMTP.MailHost = val + myLDAP.MailHost = val } if val, ok := os.LookupEnv("SMTP_PORT"); ok { if port, err := strconv.Atoi(val); err == nil { - mySMTP.MailPort = port + myLDAP.MailPort = port } else { log.Println("Invalid value for SMTP_PORT:", val) } } if val, ok := os.LookupEnv("SMTP_USER"); ok { - mySMTP.MailUser = val + myLDAP.MailUser = val } if val, ok := os.LookupEnv("SMTP_PASSWORD_FILE"); ok { if fd, err := os.Open(val); err != nil { @@ -184,13 +176,13 @@ func main() { log.Fatal(err) } else { fd.Close() - mySMTP.MailPassword = string(cnt) + myLDAP.MailPassword = string(cnt) } } else if val, ok := os.LookupEnv("SMTP_PASSWORD"); ok { - mySMTP.MailPassword = val + myLDAP.MailPassword = val } if val, ok := os.LookupEnv("SMTP_FROM"); ok { - mySMTP.MailFrom = val + myLDAP.MailFrom = val } if val, ok := os.LookupEnv("PUBLIC_URL"); ok { myPublicURL = val @@ -215,7 +207,6 @@ func main() { if err != nil || conn == nil { log.Fatalf("Unable to connect to LDAP: %s", err.Error()) } - defer conn.Close() token, dn, err := lostPasswordToken(conn, login) if err != nil { diff --git a/reset.go b/reset.go index ce9efb5..143976e 100644 --- a/reset.go +++ b/reset.go @@ -79,7 +79,6 @@ func resetPassword(w http.ResponseWriter, r *http.Request) { renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") return } - defer conn.Close() // Bind as service to perform the password change err = conn.ServiceBind()