func (h databaseHandler) getGroupName(gid int) string {
	for _, g := range h.MemGroups {
		if g.GIDNumber == gid {
			return g.Name
		}
	}
	return ""
}
Invoked for every user being returned from our database
package main
import (
	"database/sql"
	"errors"
	"fmt"
	"net"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"github.com/GeertJohan/yubigo"
	"github.com/glauth/glauth/pkg/config"
	"github.com/glauth/glauth/pkg/handler"
	"github.com/glauth/glauth/pkg/stats"
	"github.com/go-logr/logr"
	"github.com/nmcclain/ldap"
)
var configattributematcher = regexp.MustCompile(`(?i)\((?P<attribute>[a-zA-Z0-9]+)\s*=\s*(?P<value>.*)\)`)
type SqlBackend interface {
	GetDriverName() string
	CreateSchema(db *sql.DB)
	GetPrepareSymbol() string
}
type database struct {
	path string
	cnx  *sql.DB
}
type databaseHandler struct {
	backend     config.Backend
	log         logr.Logger
	cfg         *config.Config
	yubikeyAuth *yubigo.YubiAuth
	sqlBackend  SqlBackend
	database    database
	MemGroups   []config.Group
	ldohelper   handler.LDAPOpsHelper
	attmatcher  *regexp.Regexp
}
func NewDatabaseHandler(sqlBackend SqlBackend, opts ...handler.Option) handler.Handler {
	options := handler.NewOptions(opts...)
	db, err := sql.Open(sqlBackend.GetDriverName(), options.Backend.Database)
	if err != nil {
		options.Logger.Error(err, "Unable to open SQL database named '%s' error: %s", options.Backend.Database)
		os.Exit(1)
	}
	err = db.Ping()
	if err != nil {
		options.Logger.Error(err, "Unable to communicate with SQL database error: %s", options.Backend.Database)
		os.Exit(1)
	}
	dbInfo := database{
		path: options.Backend.Database,
		cnx:  db,
	}
	handler := databaseHandler{
		backend:     options.Backend,
		log:         options.Logger,
		cfg:         options.Config,
		yubikeyAuth: options.YubiAuth,
		sqlBackend:  sqlBackend,
		database:    dbInfo,
		ldohelper:   options.LDAPHelper,
		attmatcher:  configattributematcher}
	sqlBackend.CreateSchema(db)
	options.Logger.V(3).Info("Database (" + sqlBackend.GetDriverName() + "::" + options.Backend.Database + ") Plugin: Ready")
	return handler
}
func (h databaseHandler) GetBackend() config.Backend {
	return h.backend
}
func (h databaseHandler) GetLog() logr.Logger {
	return h.log
}
func (h databaseHandler) GetCfg() *config.Config {
	return h.cfg
}
func (h databaseHandler) GetYubikeyAuth() *yubigo.YubiAuth {
	return h.yubikeyAuth
}
func (h databaseHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
	return h.ldohelper.Bind(h, bindDN, bindSimplePw, conn)
}
func (h databaseHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
	return h.ldohelper.Search(h, bindDN, searchReq, conn)
}
func (h databaseHandler) Add(boundDN string, req ldap.AddRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
	return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h databaseHandler) Modify(boundDN string, req ldap.ModifyRequest, conn net.Conn) (result ldap.LDAPResultCode, err error) {
	return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h databaseHandler) Delete(boundDN string, deleteDN string, conn net.Conn) (result ldap.LDAPResultCode, err error) {
	return ldap.LDAPResultInsufficientAccessRights, nil
}
func (h databaseHandler) FindUser(userName string, searchByUPN bool) (f bool, u config.User, err error) {
	var criterion string
	if searchByUPN {
		criterion = "lower(u.mail)"
	} else {
		criterion = "lower(u.name)"
	}
	user := config.User{}
	found := false
	err = h.database.cnx.QueryRow(fmt.Sprintf(`
			SELECT u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey 
			FROM users u WHERE %s=%s`,
		criterion,
		h.sqlBackend.GetPrepareSymbol()), userName).Scan(
		&user.UIDNumber, &user.PrimaryGroup, &user.PassBcrypt, &user.PassSHA256, &user.OTPSecret, &user.Yubikey)
	if err == nil {
		found = true
	}
	return found, user, err
}
func (h databaseHandler) FindGroup(groupName string) (f bool, g config.Group, err error) {
	group := config.Group{}
	found := false
	err = h.database.cnx.QueryRow(fmt.Sprintf(`
			SELECT g.gidnumber FROM groups g WHERE lower(name)=%s`, h.sqlBackend.GetPrepareSymbol()), groupName).Scan(
		&group.GIDNumber)
	if err == nil {
		found = true
	}
	return found, group, err
}
func (h databaseHandler) FindPosixAccounts() (entrylist []*ldap.Entry, err error) {
	entries := []*ldap.Entry{}
	h.MemGroups, err = h.memoizeGroups()
	if err != nil {
		return entries, err
	}
	rows, err := h.database.cnx.Query(`
		SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups,u.givenname,u.sn,u.mail,u.loginshell,u.homedirectory,u.disabled 
		FROM users u`)
	if err != nil {
		return entries, err
	}
	defer rows.Close()
	var otherGroups string
	var disabled int
	u := config.User{}
	for rows.Next() {
		err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups, &u.GivenName, &u.SN, &u.Mail, &u.LoginShell, &u.Homedir, &disabled)
		if err != nil {
			return entries, err
		}
		u.OtherGroups = h.commaListToTable(otherGroups)
		u.Disabled = h.intToBool(disabled)
		entries = append(entries, h.getAccount(u))
	}
	return entries, nil
}
func (h databaseHandler) FindPosixGroups() (entrylist []*ldap.Entry, err error) {
	entries := []*ldap.Entry{}
	h.MemGroups, err = h.memoizeGroups()
	if err != nil {
		return entries, err
	}
	for _, g := range h.MemGroups {
		entries = append(entries, h.getGroup(g))
	}
	return entries, nil
}
func (h databaseHandler) Close(boundDn string, conn net.Conn) error {
	stats.Frontend.Add("closes", 1)
	return nil
}
func (h databaseHandler) intToBool(value int) bool {
	if value == 0 {
		return false
	}
	return true
}
func (h databaseHandler) commaListToTable(commaList string) []int {
	if len(commaList) == 0 {
		return make([]int, 0)
	}
	rowsAsStrings := strings.Split(commaList, ",")
	rowsAsInts := make([]int, len(rowsAsStrings))
	for i, v := range rowsAsStrings {
		iv, err := strconv.Atoi(v)
		if err != nil {
			return rowsAsInts
		}
		rowsAsInts[i] = iv
	}
	return rowsAsInts
}
func (h databaseHandler) memoizeGroups() ([]config.Group, error) {
	workMemGroups := make([]*config.Group, 0)
	rows, err := h.database.cnx.Query(`
		SELECT g1.name,g1.gidnumber,ig.includegroupid
		FROM groups g1 
		LEFT JOIN includegroups ig ON g1.gidnumber=ig.parentgroupid 
		LEFT JOIN groups g2 ON ig.includegroupid=g2.gidnumber`)
	if err != nil {
		return nil, errors.New("Unable to memoize groups list")
	}
	defer rows.Close()
	var groupName string
	var groupId int
	var includeId sql.NullInt64  
	var pg *config.Group
	recentId := -1  
	for rows.Next() {
		err := rows.Scan(&groupName, &groupId, &includeId)
		if err != nil {
			return nil, errors.New("Unable to memoize groups list")
		}
		if recentId != groupId {
			recentId = groupId
			g := config.Group{Name: groupName, GIDNumber: groupId}
			pg = &g  
			workMemGroups = append(workMemGroups, &g)
		}
		if includeId.Valid {
			pg.IncludeGroups = append(pg.IncludeGroups, int(includeId.Int64))
		}
	}
	memGroups := make([]config.Group, len(workMemGroups))
	for i, v := range workMemGroups {
		memGroups[i] = config.Group{Name: v.Name, GIDNumber: v.GIDNumber, IncludeGroups: v.IncludeGroups}
	}
	return memGroups, nil
}
func (h databaseHandler) getGroupMembers(gid int) []string {
	members := make(map[string]bool)
	rows, err := h.database.cnx.Query(`
			SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
			FROM users u WHERE lower(u.name)=?`,
	)
	if err != nil {
		return []string{}
	}
	defer rows.Close()
	var otherGroups string
	u := config.User{}
	for rows.Next() {
		err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
		if err != nil {
			return []string{}
		}
		if u.PrimaryGroup == gid {
			dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
			members[dn] = true
		} else {
			u.OtherGroups = h.commaListToTable(otherGroups)
			for _, othergid := range u.OtherGroups {
				if othergid == gid {
					dn := fmt.Sprintf("cn=%s,ou=%s,%s", u.Name, h.getGroupName(u.PrimaryGroup), h.backend.BaseDN)
					members[dn] = true
				}
			}
		}
	}
	for _, g := range h.MemGroups {
		if gid == g.GIDNumber {
			for _, includegroupid := range g.IncludeGroups {
				if includegroupid != gid {
					includegroupmembers := h.getGroupMembers(includegroupid)
					for _, includegroupmember := range includegroupmembers {
						members[includegroupmember] = true
					}
				}
			}
		}
	}
	m := []string{}
	for k := range members {
		m = append(m, k)
	}
	sort.Strings(m)
	return m
}
func (h databaseHandler) getGroupMemberIDs(gid int) []string {
	members := make(map[string]bool)
	rows, err := h.database.cnx.Query(`
			SELECT u.name,u.uidnumber,u.primarygroup,u.passbcrypt,u.passsha256,u.otpsecret,u.yubikey,u.othergroups
			FROM users u`)
	if err != nil {
		return []string{}
	}
	defer rows.Close()
	var otherGroups string
	u := config.User{}
	for rows.Next() {
		err := rows.Scan(&u.Name, &u.UIDNumber, &u.PrimaryGroup, &u.PassBcrypt, &u.PassSHA256, &u.OTPSecret, &u.Yubikey, &otherGroups)
		if err != nil {
			return []string{}
		}
		if u.PrimaryGroup == gid {
			members[u.Name] = true
		} else {
			u.OtherGroups = h.commaListToTable(otherGroups)
			for _, othergid := range u.OtherGroups {
				if othergid == gid {
					members[u.Name] = true
				}
			}
		}
	}
	for _, g := range h.MemGroups {
		if gid == g.GIDNumber {
			for _, includegroupid := range g.IncludeGroups {
				if includegroupid == gid {
					h.log.V(3).Info(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
				} else {
					includegroupmemberids := h.getGroupMemberIDs(includegroupid)
					for _, includegroupmemberid := range includegroupmemberids {
						members[includegroupmemberid] = true
					}
				}
			}
		}
	}
	m := []string{}
	for k := range members {
		m = append(m, k)
	}
	sort.Strings(m)
	return m
}
func (h databaseHandler) getGroupDNs(gids []int) []string {
	groups := make(map[string]bool)
	for _, gid := range gids {
		for _, g := range h.MemGroups {
			if g.GIDNumber == gid {
				dn := fmt.Sprintf("cn=%s,ou=groups,%s", g.Name, h.backend.BaseDN)
				groups[dn] = true
			}
			for _, includegroupid := range g.IncludeGroups {
				if includegroupid == gid && g.GIDNumber != gid {
					includegroupdns := h.getGroupDNs([]int{g.GIDNumber})
					for _, includegroupdn := range includegroupdns {
						groups[includegroupdn] = true
					}
				}
			}
		}
	}
	g := []string{}
	for k := range groups {
		g = append(g, k)
	}
	sort.Strings(g)
	return g
}