HTTP server changing LDAP passwords written in Go. Forked, version for chaosbit.de
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

148 lines
4.0 KiB

package main
import (
"flag"
"fmt"
"net/http"
"gopkg.in/ldap.v2"
"io/ioutil"
"net/url"
"time"
)
var (
server *string
ldapPort *int
bindQuery *string
httpPort *int
rateLimit map[string]time.Time
limitSeconds *int
)
// Connect to specified LDAP server, change `user` password from `oldpass` to `newpass`
func setNewPass(user, oldpass, newpass string) error {
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", *server, *ldapPort))
if err != nil {
return fmt.Errorf("Could not connect to LDAP server: %s", err)
}
defer l.Close()
err = l.Bind(fmt.Sprintf(*bindQuery, ldap.EscapeFilter(user)), oldpass)
if err != nil {
return fmt.Errorf("Could not bind to LDAP server: %s", err)
}
passwordModifyRequest := ldap.NewPasswordModifyRequest("", oldpass, newpass)
_, err = l.PasswordModify(passwordModifyRequest)
if err != nil {
return fmt.Errorf("Password could not be changed: %s", err.Error())
}
return nil
}
// Shows the form to a visitor
func indexHandler(w http.ResponseWriter, r *http.Request) {
address := getAddressFromRequest(r)
if isRateLimited(address) {
applyRateLimiting(address, w)
} else {
b, err := ioutil.ReadFile("index.html")
if err != nil {
w.Write([]byte("Failed to load index.html"))
} else {
w.Write(b)
}
}
}
// Is called upon connections to /change
func changeHandler(w http.ResponseWriter, r *http.Request) {
address := getAddressFromRequest(r)
if isRateLimited(address) {
applyRateLimiting(address, w)
} else {
if r.Method == "POST" {
r.ParseForm()
args := r.Form
if checkForArg(args, "user") && checkForArg(args, "oldpass") && checkForArg(args, "newpass") {
err := setNewPass(args["user"][0], args["oldpass"][0], args["newpass"][0])
if err != nil {
w.Write([]byte("Invalid credentials."))
addToRateLimit(address)
} else {
w.Write([]byte("OK"))
}
} else {
w.Write([]byte("Wrong arguments. Use user, oldpass and newpass"))
}
} else {
w.Write([]byte("Wrong method. Use POST."))
}
}
}
func checkForArg(args url.Values, key string) bool {
_, ok := args[key]
return ok
}
func addToRateLimit(ip string) {
if !isRateLimited(ip) {
rateLimit[ip] = time.Now()
}
}
func isRateLimited(ip string) bool {
_, ok := rateLimit[ip]
if ok {
if getRemainingLimit(ip) > 0 {
return true
} else {
delete(rateLimit, ip)
}
}
return false
}
func getRemainingLimit(ip string) int {
deltaTime := int(time.Now().Sub(rateLimit[ip]).Seconds())
if deltaTime < *limitSeconds {
return *limitSeconds - deltaTime
}
return 0
}
func applyRateLimiting(ip string, w http.ResponseWriter) {
w.Write([]byte(fmt.Sprintf("Hey %s, you are rate limited. Wait %d seconds.\n", ip, getRemainingLimit(ip))))
}
func getAddressFromRequest(r *http.Request) string {
address := r.Header.Get("X-Forwarded-For")
if address == "" {
address = r.RemoteAddr
}
return address
}
func main() {
server = flag.String("ldapServer", "localhost", "Hostname or address of the LDAP server to use")
ldapPort = flag.Int("ldapPort", 389, "Port number of the LDAP server")
bindQuery = flag.String("ldapBind", "uid=%s,dc=example,dc=org", "Path to bind to. %s will be replaced by the username")
httpPort = flag.Int("httpPort", 8080, "Port number to listen for HTTP requests")
limitSeconds = flag.Int("limitSeconds", 10, "Seconds to wait until another request may be executed")
rateLimit = make(map[string]time.Time)
flag.Parse()
http.HandleFunc("/", indexHandler)
http.HandleFunc("/change", changeHandler)
http.ListenAndServe(fmt.Sprintf(":%d", *httpPort), nil)
}