fix(ldap): add Close() method and defer conn.Close() at all call sites

LDAP connections were never closed, leaking TCP connections on every
request. Also refactors change.go from chained else-if to early returns
for cleaner defer placement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-03-16 14:40:40 +07:00
commit 4a68d0700d
7 changed files with 57 additions and 24 deletions

View file

@ -115,6 +115,7 @@ func addyAliasAPI(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
defer conn.Close()
err = conn.ServiceBind() err = conn.ServiceBind()
if err != nil { if err != nil {
@ -197,6 +198,7 @@ func addyAliasAPIDelete(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
defer conn.Close()
err = conn.ServiceBind() err = conn.ServiceBind()
if err != nil { if err != nil {

View file

@ -67,31 +67,51 @@ func changePassword(w http.ResponseWriter, r *http.Request) {
// Check the two new passwords are identical // Check the two new passwords are identical
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") { if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.") renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.")
} else if len(r.PostFormValue("login")) == 0 { return
}
if len(r.PostFormValue("login")) == 0 {
renderError(http.StatusNotAcceptable, "Please provide a valid login") renderError(http.StatusNotAcceptable, "Please provide a valid login")
} else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil { return
}
if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error()) renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error())
} else { return
}
conn, err := myLDAP.Connect() conn, err := myLDAP.Connect()
if err != nil || conn == nil { if err != nil || conn == nil {
log.Println(err) log.Println(err)
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
} else if err := conn.ServiceBind(); err != nil { return
}
defer conn.Close()
if err := conn.ServiceBind(); err != nil {
log.Println(err) log.Println(err)
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
} else if dn, err := conn.SearchDN(r.PostFormValue("login"), true); err != nil { return
}
dn, err := conn.SearchDN(r.PostFormValue("login"), true)
if err != nil {
log.Println(err) log.Println(err)
// User not found: perform a dummy bind to prevent username enumeration via timing. // User not found: perform a dummy bind to prevent username enumeration via timing.
conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password")) conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password"))
renderError(http.StatusUnauthorized, "Invalid login or password.") renderError(http.StatusUnauthorized, "Invalid login or password.")
} else if err := conn.Bind(dn, r.PostFormValue("password")); err != nil { return
}
if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
log.Println(err) log.Println(err)
renderError(http.StatusUnauthorized, "Invalid login or password.") renderError(http.StatusUnauthorized, "Invalid login or password.")
} else if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil { return
}
if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
log.Println(err) log.Println(err)
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
} else { return
}
displayMsg(w, "Password successfully changed!", http.StatusOK) displayMsg(w, "Password successfully changed!", http.StatusOK)
}
}
} }

View file

@ -6,6 +6,8 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"strconv"
"time"
"github.com/amoghe/go-crypt" "github.com/amoghe/go-crypt"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
@ -58,6 +60,10 @@ type LDAPConn struct {
connection *ldap.Conn connection *ldap.Conn
} }
func (l LDAPConn) Close() {
l.connection.Close()
}
func (l LDAPConn) ServiceBind() error { func (l LDAPConn) ServiceBind() error {
return l.connection.Bind(l.ServiceDN, l.ServicePassword) return l.connection.Bind(l.ServiceDN, l.ServicePassword)
} }
@ -133,6 +139,7 @@ func (l LDAPConn) ChangePassword(dn string, rawpassword string) error {
modify := ldap.NewModifyRequest(dn, nil) modify := ldap.NewModifyRequest(dn, nil)
modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd}) modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd})
modify.Replace("shadowLastChange", []string{strconv.FormatInt(time.Now().Unix()/86400, 10)})
return l.connection.Modify(modify) return l.connection.Modify(modify)
} }

View file

@ -78,6 +78,7 @@ func login(login string, password string) ([]*ldap.EntryAttribute, error) {
if err != nil || conn == nil { if err != nil || conn == nil {
return nil, err return nil, err
} }
defer conn.Close()
if err = conn.ServiceBind(); err != nil { if err = conn.ServiceBind(); err != nil {
return nil, err return nil, err

View file

@ -129,6 +129,7 @@ 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."}) displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]any{"error": "Unable to process your request. Please try again later."})
return return
} }
defer conn.Close()
// Generate the token // Generate the token
token, dn, err := lostPasswordToken(conn, r.PostFormValue("login")) token, dn, err := lostPasswordToken(conn, r.PostFormValue("login"))

View file

@ -207,6 +207,7 @@ func main() {
if err != nil || conn == nil { if err != nil || conn == nil {
log.Fatalf("Unable to connect to LDAP: %s", err.Error()) log.Fatalf("Unable to connect to LDAP: %s", err.Error())
} }
defer conn.Close()
token, dn, err := lostPasswordToken(conn, login) token, dn, err := lostPasswordToken(conn, login)
if err != nil { if err != nil {

View file

@ -79,6 +79,7 @@ func resetPassword(w http.ResponseWriter, r *http.Request) {
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.") renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
return return
} }
defer conn.Close()
// Bind as service to perform the password change // Bind as service to perform the password change
err = conn.ServiceBind() err = conn.ServiceBind()