Skip to content
Snippets Groups Projects
Commit 834f68fe authored by Theo's avatar Theo :troll:
Browse files

Refactor: remodel some create.go functions and add comments

parent 99e8d1a1
Branches
No related tags found
No related merge requests found
...@@ -14,6 +14,8 @@ var rootCmd = &cobra.Command{ ...@@ -14,6 +14,8 @@ var rootCmd = &cobra.Command{
with the cobra library that took inspiration from the old with the cobra library that took inspiration from the old
useradm.py. It takes care of LDAP configuration and was useradm.py. It takes care of LDAP configuration and was
made as an update for the previous version.`, made as an update for the previous version.`,
// since we already print the errors in Execute()
// not having this would print the error twice :p
SilenceErrors: true, SilenceErrors: true,
} }
......
...@@ -8,6 +8,9 @@ import ( ...@@ -8,6 +8,9 @@ import (
var userCmd = &cobra.Command{ var userCmd = &cobra.Command{
Use: "user", Use: "user",
Short: "User subcommand", Short: "User subcommand",
Long: `Subcommand for managing unique users in general.
If what you are trying to do is create a lot of
new users, please do so with the bulk subcommand.`,
} }
func init() { func init() {
......
...@@ -3,8 +3,6 @@ package user ...@@ -3,8 +3,6 @@ package user
import ( import (
"os" "os"
"fmt" "fmt"
"log"
"sort"
"time" "time"
"regexp" "regexp"
"os/exec" "os/exec"
...@@ -18,15 +16,33 @@ import ( ...@@ -18,15 +16,33 @@ import (
) )
const ( const (
MAIL_ALIAS_FILE = "/etc/aliases" MAIL_ALIAS_FILE = "/etc/aliases" // aliases db path for query
DEF_PERMISSION = "0700" DEF_PERMISSION = "0700" // default user dir permission
BLK_PERMISSION = "0000" BLK_PERMISSION = "0000" // user dir permission if blocked
HTML_BASE_PERM = "0755" HTML_BASE_PERM = "0755" // set public_html to this on creation
MAX_VARIANCE = 50 MAX_VARIANCE = 50 // tries made to create login automatically
MIN_UID = 1000 MIN_UID = 1000
MAX_UID = 6000 MAX_UID = 6000
) )
type userOpts struct {
GRR string
GID string
UID string
Resp string
Name string
Nobkp string
Ltype string
Shell string
Passwd string
Webdir string
Status string
Course string
Expiry string
Homedir string
Confirm bool
}
var CreateUserCmd = &cobra.Command{ var CreateUserCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create new user", Short: "Create new user",
...@@ -34,7 +50,8 @@ var CreateUserCmd = &cobra.Command{ ...@@ -34,7 +50,8 @@ var CreateUserCmd = &cobra.Command{
} }
func init() { func init() {
// Possible Flags // possible flags
// FIXME: maybe leave less flags for user input
CreateUserCmd.Flags().StringP("grr", "r", "_", "User GRR, required for ini type") CreateUserCmd.Flags().StringP("grr", "r", "_", "User GRR, required for ini type")
CreateUserCmd.Flags().StringP("type", "t", "ini", "Type of auto-generated login: ini, first or last") CreateUserCmd.Flags().StringP("type", "t", "ini", "Type of auto-generated login: ini, first or last")
CreateUserCmd.Flags().StringP("path", "w", "", "Full path to webdir, /home/html/inf/login if empty") CreateUserCmd.Flags().StringP("path", "w", "", "Full path to webdir, /home/html/inf/login if empty")
...@@ -50,16 +67,17 @@ func init() { ...@@ -50,16 +67,17 @@ func init() {
CreateUserCmd.Flags().StringP("course", "c", "_", "User course/minicourse, even temp ones") CreateUserCmd.Flags().StringP("course", "c", "_", "User course/minicourse, even temp ones")
CreateUserCmd.Flags().StringP("nobkp", "b", "", "User nobackup directory path") CreateUserCmd.Flags().StringP("nobkp", "b", "", "User nobackup directory path")
// Required Flags // required flags
CreateUserCmd.MarkFlagRequired("name") CreateUserCmd.MarkFlagRequired("name")
CreateUserCmd.MarkFlagRequired("group") CreateUserCmd.MarkFlagRequired("group")
CreateUserCmd.MarkFlagRequired("course")
CreateUserCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") CreateUserCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt")
} }
func createUserFunc(cmd *cobra.Command, args []string) error { func createUserFunc(cmd *cobra.Command, args []string) error {
success := false success := false
// Creates model from users input // creates model from users input
usr, confirm, err := createNewUserModel(cmd) usr, confirm, err := createNewUserModel(cmd)
if err != nil { return err } if err != nil { return err }
...@@ -70,12 +88,11 @@ func createUserFunc(cmd *cobra.Command, args []string) error { ...@@ -70,12 +88,11 @@ func createUserFunc(cmd *cobra.Command, args []string) error {
} }
}() }()
// Prints info for confirmation // prints info for confirmation
//fmt.Printf("%v\n", usr.FullToString()) fmt.Printf("%v\n", usr.FullToString()) // for debug
fmt.Printf("%v\n Passwd: %v\n\n", usr.ToString(), usr.Password) //fmt.Printf("%v\n Passwd: %v\n\n", usr.ToString(), usr.Password)
confirmationPrompt(confirm, "creation") confirmationPrompt(confirm, "creation")
// Generate the new password
if usr.Password == "[auto-generate]" { if usr.Password == "[auto-generate]" {
usr.Password = genPassword() usr.Password = genPassword()
} }
...@@ -102,205 +119,121 @@ func createUserFunc(cmd *cobra.Command, args []string) error { ...@@ -102,205 +119,121 @@ func createUserFunc(cmd *cobra.Command, args []string) error {
return nil return nil
} }
// creates and validates user inputs into the User model
func createNewUserModel(cmd *cobra.Command) (model.User, bool, error) { func createNewUserModel(cmd *cobra.Command) (model.User, bool, error) {
var u model.User var u model.User
users, err := getUsers() users, err := getUsers()
if err != nil { return u, false, err } if err != nil {
userGRR, err := cmd.Flags().GetString("grr")
if err != nil { return u, false, err }
userResp, err := cmd.Flags().GetString("resp")
if err != nil { return u, false, err }
userName, err := cmd.Flags().GetString("name")
if err != nil { return u, false, err }
userType, err := cmd.Flags().GetString("type")
if err != nil { return u, false, err }
userGID, err := cmd.Flags().GetString("group")
if err != nil { return u, false, err }
userUID, err := cmd.Flags().GetString("login")
if err != nil { return u, false, err }
confirm, err := cmd.Flags().GetBool("confirm")
if err != nil { return u, false, err }
userShell, err := cmd.Flags().GetString("shell")
if err != nil { return u, false, err }
userWebdir, err := cmd.Flags().GetString("path")
if err != nil { return u, false, err }
userStatus, err := cmd.Flags().GetString("status")
if err != nil { return u, false, err }
userCourse, err := cmd.Flags().GetString("course")
if err != nil { return u, false, err }
userExpiry, err := cmd.Flags().GetString("expiry")
if err != nil { return u, false, err }
userHomedir, err := cmd.Flags().GetString("homedir")
if err != nil { return u, false, err }
userPassword, err := cmd.Flags().GetString("passwd")
if err != nil { return u, false, err }
userNobackup, err := cmd.Flags().GetString("nobkp")
if err != nil { return u, false, err }
groups, err := getGroups()
if err != nil { return u, false, err }
err = validateExpiry(userExpiry)
if err != nil { return u, false, err }
err = validateStatus(userStatus)
if err != nil { return u, false, err }
err = validateGID(groups, userGID)
if err != nil { return u, false, err }
err = validateUID(users, userUID)
if err != nil { return u, false, err }
err = validateGRR(users, userGRR)
if err != nil { return u, false, err }
if userStatus == "Blocked" { userShell = "/bin/false" }
if userType == "ini" && userGRR == "_" {
err := fmt.Errorf("GRR is required for \"ini\" login type")
return u, false, err return u, false, err
} }
if userUID == "" { groups, err := getGroups()
used, variance := true, 0 if err != nil {
for used { return u, false, err
userUID = genLogin(userName, userGRR, userType, variance)
used = userUID == "" || userUID == "_" ||
loginExists(users, userUID) ||
mailAliasExists(userUID)
variance++
if variance > MAX_VARIANCE {
log.Fatalf("Could't generate login automatically, please inform the desired login\n")
}
}
}
userHomedir = genHomedirPath(userUID, userHomedir, userGID)
err = validatePath(userHomedir)
if err != nil { return u, false, err }
userNobackup = genNobackupPath(userUID, userGID, userNobackup)
err = validatePath(userNobackup)
if err != nil { return u, false, err }
userWebdir = genWebdirPath(userUID, userWebdir)
err = validatePath(userWebdir)
if err != nil { return u, false, err }
u = model.User{
UID: userUID,
GID: userGID,
GRR: userGRR,
Resp: userResp,
Name: userName,
Ltype: userType,
Shell: userShell,
Status: userStatus,
Expiry: userExpiry,
Course: userCourse,
Webdir: userWebdir,
Homedir: userHomedir,
Nobackup: userNobackup,
Password: ifThenElse(userPassword != "", userPassword, "[auto-generate]"),
} }
u = genGecos(u) opts, err := retrieveOpts(cmd)
newUIDNumber, err := getNewUIDNumber()
if err != nil { if err != nil {
err = fmt.Errorf("Failed to generate new UIDNumber for user: %v", err)
return u, false, err return u, false, err
} }
u.UIDNumber = newUIDNumber
for key, value := range groups { if err := validateInputs(opts)
if value == u.GID { err != nil {
u.GIDNumber = key return u, false, err
}
} }
u.DN = "uid=" + u.UID + ",ou=usuarios,dc=c3local" if opts.Status == "Blocked" {
opts.Shell = "/bin/false"
return u, confirm, nil
} }
func ifThenElse(condition bool, a string, b string) string { if opts.Ltype == "ini" && opts.GRR == "_" {
if condition { return a } return u, false, fmt.Errorf("GRR is required for \"ini\" login type")
return b
} }
func genHomedirPath(login string, homedir string, group string) string { if opts.UID == "" {
if homedir != "" { return homedir } opts.UID, err = genUniqueUID(opts.Name, opts.GRR, opts.Ltype, users)
return filepath.Join("/home", group, login) if err != nil {
return u, false, err
}
} }
func genWebdirPath(login string, webdir string) string { opts.Homedir, err = genDirPath("/home", opts.GID, opts.UID, opts.Homedir)
if webdir != "" { return webdir } if err != nil {
return filepath.Join("/home/html/inf", login) return u, false, err
} }
func genNobackupPath(login string, group string, nobkp string) string { opts.Nobkp, err = genDirPath("/nobackup", opts.GID, opts.UID, opts.Nobkp)
if nobkp != "" { return nobkp } if err != nil {
return filepath.Join("/nobackup", group, login) return u, false, err
} }
func genGecos(user model.User) model.User { opts.Webdir, err = genDirPath("/home/html/inf", "", opts.UID, opts.Webdir)
gecos := user.Name + "," if err != nil {
gecos += user.GRR + "," return u, false, err
gecos += user.Resp + ","
gecos += user.Course + ","
gecos += user.Status + ","
gecos += user.Expiry + ","
gecos += user.Ltype + ","
gecos += user.Webdir + ","
gecos += user.Nobackup
user.Gecos = gecos
return user
} }
func validatePath(path string) error { u = model.User{
_, err := os.Stat(path) UID: opts.UID,
if os.IsNotExist(err) { return nil } GID: opts.GID,
return fmt.Errorf("Path \"%v\" already exists, please provide a new path", path) GRR: opts.GRR,
Resp: opts.Resp,
Name: opts.Name,
Ltype: opts.Ltype,
Shell: opts.Shell,
Status: opts.Status,
Expiry: opts.Expiry,
Course: opts.Course,
Webdir: opts.Webdir,
Homedir: opts.Homedir,
Nobackup: opts.Nobkp,
Password: ifThenElse(opts.Passwd != "", opts.Passwd, "[auto-generate]"),
} }
func validateExpiry(expiry string) error { u = genGecos(u)
if expiry == "_" { return nil }
parts := strings.Split(expiry, ".") // get a new UIDNumber
if !isValidDate(parts) { newUIDNumber, err := getNewUIDNumber()
err := fmt.Errorf("Malformed expiry date string, use \"dd.mm.yy\"") if err != nil {
return err return u, false, fmt.Errorf("failed to generate new UIDNumber for user: %v", err)
}
return nil
} }
u.UIDNumber = newUIDNumber
func isValidDate(arr []string) bool { // assign GIDNumber by traversing the groups
if len(arr) != 3 { for key, val := range groups {
return false if val == u.GID {
u.GIDNumber = key
break
}
} }
// Convert to int u.DN = "uid=" + u.UID + ",ou=usuarios,dc=c3local"
day, err1 := strconv.Atoi(arr[0])
month, err2 := strconv.Atoi(arr[1])
year, err3 := strconv.Atoi(arr[2])
if err1 != nil || err2 != nil || err3 != nil { return u, opts.Confirm, nil
return false
} }
// Ensure year is two digits func genDirPath(base, group, login, input string) (string, error) {
if year < 0 || year > 99 { if input != "" { return input, nil }
return false p := filepath.Join(base, group, login)
return p, validatePath(p)
} }
// Validate the date func genGecos(u model.User) model.User {
fullYear := 2000 + year gecos := u.Name + ","
t := time.Date(fullYear, time.Month(month), day, 0, 0, 0, 0, time.UTC) gecos += u.GRR + ","
return t.Day() == day && t.Month() == time.Month(month) gecos += u.Resp + ","
gecos += u.Course + ","
gecos += u.Status + ","
gecos += u.Expiry + ","
gecos += u.Ltype + ","
gecos += u.Webdir + ","
gecos += u.Nobackup
u.Gecos = gecos
return u
} }
// basically takes the initials and the grr year.
// if variance increases we take next letter of
// 1st name, then next letter of 2nd name, etc...
func genLoginIni(name string, grr string, variance int) string { func genLoginIni(name string, grr string, variance int) string {
login := "" login := ""
parts := formatName(name) parts := formatName(name)
...@@ -320,11 +253,42 @@ func genLoginIni(name string, grr string, variance int) string { ...@@ -320,11 +253,42 @@ func genLoginIni(name string, grr string, variance int) string {
} }
} }
login += grr[2:4] return login + grr[2:4]
}
// parent function for generating a login automatically
func genLogin(name string, grr string, ltype string, variance int) string {
var login string
switch ltype {
case "", "ini":
login = genLoginIni(name, grr, variance)
case "first":
login = genLoginFirst(name, variance)
case "last":
login = genLoginLast(name, variance)
}
return login return login
} }
// removes connectives, leave all lowecase and splits the name
func formatName(name string) []string {
connectives := map[string]bool{"da": true, "de": true, "di": true,
"do": true, "das": true, "dos": true, "von": true}
splitName := strings.Fields(name)
var parts []string
for _, part := range splitName {
lowerPart := strings.ToLower(part)
if !connectives[lowerPart] {
parts = append(parts, string(lowerPart))
}
}
return parts
}
// basically takes 1st name and the initial of the 2nd.
// if variance increases we take more initials, and if
// it keeps going we put more letters in each name
func genLoginFirst(name string, variance int) string { func genLoginFirst(name string, variance int) string {
parts := formatName(name) parts := formatName(name)
...@@ -352,6 +316,8 @@ func genLoginFirst(name string, variance int) string { ...@@ -352,6 +316,8 @@ func genLoginFirst(name string, variance int) string {
return login return login
} }
// basically takes initials up to last name and then appends it.
// if variance increases we put more letters in each name
func genLoginLast(name string, variance int) string { func genLoginLast(name string, variance int) string {
login := "" login := ""
parts := formatName(name) parts := formatName(name)
...@@ -380,82 +346,24 @@ func genLoginLast(name string, variance int) string { ...@@ -380,82 +346,24 @@ func genLoginLast(name string, variance int) string {
return login return login
} }
func genLogin(name string, grr string, ltype string, variance int) string { func genUniqueUID(name, grr, ltype string, users []model.User) (string, error) {
var login string var uid string
switch ltype { used, variance := true, 0
case "", "ini": for used {
login = genLoginIni(name, grr, variance) uid = genLogin(name, grr, ltype, variance)
case "first":
login = genLoginFirst(name, variance)
case "last":
login = genLoginLast(name, variance)
}
return login
}
func validateStatus(status string) error {
if status != "Blocked" && status != "Active" {
err := fmt.Errorf("User status can only be \"Active\" or \"Blocked\"")
return err
}
return nil
}
func validateGRR(users []model.User, grr string) error {
if grr == "_" { return nil }
isValid, _ := regexp.MatchString(`^\d{8}$`, grr) // is 8 digit number
if !isValid {
err := fmt.Errorf("Malformed GRR string, must be 8 digit number")
return err
}
if grrExists(users, grr) {
err := fmt.Errorf(`The informed GRR already exists in LDAP database
Note: To search for the account use "useradm user show -r %s"`, grr)
return err
}
return nil
}
func validateGID(groups map[string]string, group string) error {
for _, value := range groups {
if value == group {
return nil
}
}
err := fmt.Errorf("Could't find group \"%v\" in LDAP database", group)
return err
}
func validateUID(users []model.User, login string) error {
for i := range users {
if users[i].UID == login {
err := fmt.Errorf(`The informed Login already exists in LDAP database
Note: To search for the account use "useradm user show -l %s"`, login)
return err
}
}
return nil
}
func formatName(name string) []string { // already taken or alias for it exists :(
connectives := map[string]bool{"da": true, "de": true, "di": true, used = loginExists(users, uid) || mailAliasExists(uid)
"do": true, "das": true, "dos": true, "von": true}
splitName := strings.Fields(name)
var parts []string
for _, part := range splitName { variance++
lowerPart := strings.ToLower(part) if variance > MAX_VARIANCE {
if !connectives[lowerPart] { return "", fmt.Errorf("Could't generate login automatically, please inform the desired login\n")
parts = append(parts, string(lowerPart))
} }
} }
return parts return uid, nil
} }
// Queries to check if the alias exists // queries to check if the alias exists
func mailAliasExists(alias string) bool { func mailAliasExists(alias string) bool {
cmd := exec.Command("/usr/sbin/postalias", "-q", alias, MAIL_ALIAS_FILE) cmd := exec.Command("/usr/sbin/postalias", "-q", alias, MAIL_ALIAS_FILE)
cmd.Stdout = nil cmd.Stdout = nil
...@@ -463,7 +371,8 @@ func mailAliasExists(alias string) bool { ...@@ -463,7 +371,8 @@ func mailAliasExists(alias string) bool {
return cmd.Run() == nil return cmd.Run() == nil
} }
func getNewUIDNumberLinear() (string, error) { // finds next available uidNumber (MEX from uid group)
func getNewUIDNumber() (string, error) {
uids, err := getUIDs() uids, err := getUIDs()
if err != nil { if err != nil {
return "", err return "", err
...@@ -471,9 +380,9 @@ func getNewUIDNumberLinear() (string, error) { ...@@ -471,9 +380,9 @@ func getNewUIDNumberLinear() (string, error) {
candidate := MIN_UID candidate := MIN_UID
for _, uid := range uids { for _, uid := range uids {
if uid == candidate { // Check if taken if uid == candidate { // check if taken
candidate++ candidate++
} else if uid > candidate { // Found a gap } else if uid > candidate { // found a gap
break break
} }
} }
...@@ -484,24 +393,7 @@ func getNewUIDNumberLinear() (string, error) { ...@@ -484,24 +393,7 @@ func getNewUIDNumberLinear() (string, error) {
return strconv.Itoa(candidate), nil return strconv.Itoa(candidate), nil
} }
// Finds next available uidNumber // generates a LDAP request and adds the user to LDAP
func getNewUIDNumber() (string, error) {
uids, err := getUIDs()
if err != nil {
return "", err
}
for candidate := MIN_UID; candidate <= MAX_UID; candidate++ {
i := sort.Search(len(uids), func(j int) bool {
return uids[j] >= candidate
})
if i == len(uids) || uids[i] != candidate {
// Candidate UID is not in the list, so this is available.
return strconv.Itoa(candidate), nil
}
}
return "", fmt.Errorf("no more available UID numbers")
}
func addUserLDAP(u model.User) error { func addUserLDAP(u model.User) error {
l, err := connLDAP() l, err := connLDAP()
if err != nil { if err != nil {
...@@ -527,6 +419,7 @@ func addUserLDAP(u model.User) error { ...@@ -527,6 +419,7 @@ func addUserLDAP(u model.User) error {
return nil return nil
} }
// creates a KerberosPrincipal for the user
func addKerberosPrincipal(login string) error { func addKerberosPrincipal(login string) error {
cmd := exec.Command("kadmin.local", "-q", cmd := exec.Command("kadmin.local", "-q",
fmt.Sprintf("addprinc -policy padrao -randkey -x dn=uid=%s,ou=usuarios,dc=c3local %s", login, login)) fmt.Sprintf("addprinc -policy padrao -randkey -x dn=uid=%s,ou=usuarios,dc=c3local %s", login, login))
...@@ -539,6 +432,7 @@ func addKerberosPrincipal(login string) error { ...@@ -539,6 +432,7 @@ func addKerberosPrincipal(login string) error {
return nil return nil
} }
// sets the password for the user (via kerberos)
func modKerberosPassword(login, password string) error { func modKerberosPassword(login, password string) error {
cmd := exec.Command("kadmin.local", "-q", cmd := exec.Command("kadmin.local", "-q",
fmt.Sprintf("cpw -pw %s %s", password, login)) fmt.Sprintf("cpw -pw %s %s", password, login))
...@@ -555,6 +449,7 @@ func modKerberosPassword(login, password string) error { ...@@ -555,6 +449,7 @@ func modKerberosPassword(login, password string) error {
func createUserDirs(u model.User) error { func createUserDirs(u model.User) error {
success := false success := false
defer func() { defer func() {
if !success { if !success {
fmt.Println("Error found creating dirs, cleaning up...") fmt.Println("Error found creating dirs, cleaning up...")
...@@ -584,7 +479,7 @@ func createHome(u model.User, homeDir string) error { ...@@ -584,7 +479,7 @@ func createHome(u model.User, homeDir string) error {
perm = BLK_PERMISSION perm = BLK_PERMISSION
} }
// Create directory // create directory
cmd := exec.Command("mkdir", "-p", homeDir) cmd := exec.Command("mkdir", "-p", homeDir)
cmd.Stdout = nil cmd.Stdout = nil
cmd.Stderr = nil cmd.Stderr = nil
...@@ -592,19 +487,19 @@ func createHome(u model.User, homeDir string) error { ...@@ -592,19 +487,19 @@ func createHome(u model.User, homeDir string) error {
return fmt.Errorf("Failed to create home directory: %w", err) return fmt.Errorf("Failed to create home directory: %w", err)
} }
// Copy /etc/skel // copy /etc/skel
cmd = exec.Command("cp", "-r", "/etc/skel/.", homeDir) cmd = exec.Command("cp", "-r", "/etc/skel/.", homeDir)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to copy /etc/skel contents: %w", err) return fmt.Errorf("Failed to copy /etc/skel contents: %w", err)
} }
// Change permissions // change permissions
cmd = exec.Command("chmod", perm, homeDir) cmd = exec.Command("chmod", perm, homeDir)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to set permissions: %w", err) return fmt.Errorf("Failed to set permissions: %w", err)
} }
// Change ownership // change ownership
cmd = exec.Command("chown", "-R", fmt.Sprintf("%s:%s", u.UID, u.GID), homeDir) cmd = exec.Command("chown", "-R", fmt.Sprintf("%s:%s", u.UID, u.GID), homeDir)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to change ownership: %w", err) return fmt.Errorf("Failed to change ownership: %w", err)
...@@ -627,7 +522,7 @@ func createWeb(u model.User) error { ...@@ -627,7 +522,7 @@ func createWeb(u model.User) error {
} }
}() }()
// Create directory // create directory
cmd := exec.Command("mkdir", "-p", u.Webdir) cmd := exec.Command("mkdir", "-p", u.Webdir)
cmd.Stdout = nil cmd.Stdout = nil
cmd.Stderr = nil cmd.Stderr = nil
...@@ -635,30 +530,31 @@ func createWeb(u model.User) error { ...@@ -635,30 +530,31 @@ func createWeb(u model.User) error {
return fmt.Errorf("Failed to create web directory: %w", err) return fmt.Errorf("Failed to create web directory: %w", err)
} }
// Create index // create index
cmd = exec.Command("touch", filepath.Join(u.Webdir, "index.html")) cmd = exec.Command("touch", filepath.Join(u.Webdir, "index.html"))
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to create index.html: %w", err) return fmt.Errorf("Failed to create index.html: %w", err)
} }
// Create link in users home // create link in users home
cmd = exec.Command("ln", "-s", u.Webdir, filepath.Join(u.Homedir, "public_html")) cmd = exec.Command("ln", "-s", u.Webdir, filepath.Join(u.Homedir, "public_html"))
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to create link public_html: %w", err) return fmt.Errorf("Failed to create link public_html: %w", err)
} }
// Change permissions // change permissions
cmd = exec.Command("chmod", perm, u.Webdir) cmd = exec.Command("chmod", perm, u.Webdir)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to set permissions: %w", err) return fmt.Errorf("Failed to set permissions: %w", err)
} }
// Change ownership // change ownership
cmd = exec.Command("chown", "-R", fmt.Sprintf("%s:%s", u.UID, u.GID), u.Webdir) cmd = exec.Command("chown", "-R", fmt.Sprintf("%s:%s", u.UID, u.GID), u.Webdir)
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to change ownership: %w", err) return fmt.Errorf("Failed to change ownership: %w", err)
} }
// set permission for public_html
cmd = exec.Command("chmod", HTML_BASE_PERM, filepath.Join(u.Homedir, "public_html")) cmd = exec.Command("chmod", HTML_BASE_PERM, filepath.Join(u.Homedir, "public_html"))
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("Failed to create set permissions for public_html: %w", err) return fmt.Errorf("Failed to create set permissions for public_html: %w", err)
...@@ -667,3 +563,190 @@ func createWeb(u model.User) error { ...@@ -667,3 +563,190 @@ func createWeb(u model.User) error {
success = true success = true
return nil return nil
} }
func validatePath(path string) error {
_, err := os.Stat(path)
if os.IsNotExist(err) { return nil }
return fmt.Errorf("Path \"%v\" already exists, please provide a new path", path)
}
func validateExpiry(expiry string) error {
if expiry == "_" { return nil }
parts := strings.Split(expiry, ".")
if !isValidDate(parts) {
err := fmt.Errorf("Malformed expiry date string, use \"dd.mm.yy\"")
return err
}
return nil
}
func validateStatus(status string) error {
if status != "Blocked" && status != "Active" {
err := fmt.Errorf("User status can only be \"Active\" or \"Blocked\"")
return err
}
return nil
}
func validateGRR(grr string) error {
// OK if empty, only "ini" login type requires it and we check :)
if grr == "_" { return nil }
users, err := getUsers()
if err != nil { return err }
isValid, _ := regexp.MatchString(`^\d{8}$`, grr) // is 8 digit number
if !isValid {
err := fmt.Errorf("Malformed GRR string, must be 8 digit number")
return err
}
if grrExists(users, grr) {
err := fmt.Errorf(`The informed GRR already exists in LDAP database
Note: To search for the account use "useradm user show -r %s"`, grr)
return err
}
return nil
}
func validateGID(group string) error {
var err error
groups, err := getGroups()
if err != nil { return err }
for _, value := range groups {
if value == group {
return nil
}
}
err = fmt.Errorf("Could't find group \"%v\" in LDAP database", group)
return err
}
func validateUID(login string) error {
users, err := getUsers()
if err != nil { return err }
res := searchUser(users, false, login, "", "", "", "", "")
if len(res) != 0 {
return fmt.Errorf(`The informed Login already exists in LDAP database
Note: To search for the account use "useradm user show -l %s"`, login)
}
return nil
}
func validateInputs(opts userOpts) error {
var err error
err = validateGID(opts.GID)
if err != nil { return err }
err = validateGRR(opts.GRR)
if err != nil { return err }
err = validateExpiry(opts.Expiry)
if err != nil { return err }
err = validateStatus(opts.Status)
if err != nil { return err }
// it’s OK if UID is empty here, we generate it later :)
if opts.UID != "" {
err := validateUID(opts.UID)
if err != nil { return err }
}
return nil
}
func getFlagString(cmd *cobra.Command, flag string) (string, error) {
flagValue, err := cmd.Flags().GetString(flag)
if err != nil {
return "", fmt.Errorf("Failed to get flag %q: %w", flag, err)
}
return flagValue, nil
}
func retrieveOpts(cmd *cobra.Command) (userOpts, error) {
var opts userOpts
var err error
opts.GRR, err = getFlagString(cmd, "grr")
if err != nil { return opts, err }
opts.Resp, err = getFlagString(cmd, "resp")
if err != nil { return opts, err }
opts.Name, err = getFlagString(cmd, "name")
if err != nil { return opts, err }
opts.GID, err = getFlagString(cmd, "group")
if err != nil { return opts, err }
opts.UID, err = getFlagString(cmd, "login")
if err != nil { return opts, err }
opts.Ltype, err = getFlagString(cmd, "type")
if err != nil { return opts, err }
opts.Shell, err = getFlagString(cmd, "shell")
if err != nil { return opts, err }
opts.Webdir, err = getFlagString(cmd, "path")
if err != nil { return opts, err }
opts.Nobkp, err = getFlagString(cmd, "nobkp")
if err != nil { return opts, err }
opts.Status, err = getFlagString(cmd, "status")
if err != nil { return opts, err }
opts.Course, err = getFlagString(cmd, "course")
if err != nil { return opts, err }
opts.Expiry, err = getFlagString(cmd, "expiry")
if err != nil { return opts, err }
opts.Passwd, err = getFlagString(cmd, "passwd")
if err != nil { return opts, err }
opts.Homedir, err = getFlagString(cmd, "homedir")
if err != nil { return opts, err }
opts.Confirm, err = cmd.Flags().GetBool("confirm")
if err != nil { return opts, err }
return opts, nil
}
func isValidDate(arr []string) bool {
if len(arr) != 3 {
return false
}
// convert to int
day, err1 := strconv.Atoi(arr[0])
mth, err2 := strconv.Atoi(arr[1])
year, err3 := strconv.Atoi(arr[2])
if err1 != nil || err2 != nil || err3 != nil {
return false
}
// ensure year is two digits
if year < 0 || year > 99 {
return false
}
// validate the date
fullYear := 2000 + year
t := time.Date(fullYear, time.Month(mth), day, 0, 0, 0, 0, time.UTC)
return t.Day() == day && t.Month() == time.Month(mth)
}
func ifThenElse(condition bool, a string, b string) string {
if condition { return a }
return b
}
...@@ -37,8 +37,10 @@ func init() { ...@@ -37,8 +37,10 @@ func init() {
func deleteUserFunc(cmd *cobra.Command, args []string) error { func deleteUserFunc(cmd *cobra.Command, args []string) error {
success := false success := false
userLogin, err := cmd.Flags().GetString("login")
userLogin, err := getFlagString(cmd, "login")
if err != nil { return err } if err != nil { return err }
confirm, err := cmd.Flags().GetBool("confirm") confirm, err := cmd.Flags().GetBool("confirm")
if err != nil { return err } if err != nil { return err }
...@@ -68,25 +70,31 @@ func deleteUserFunc(cmd *cobra.Command, args []string) error { ...@@ -68,25 +70,31 @@ func deleteUserFunc(cmd *cobra.Command, args []string) error {
if err != nil { return err } if err != nil { return err }
fmt.Printf("\nUser removed!\n") fmt.Printf("\nUser removed!\n")
success = true success = true
return nil return nil
} }
// searches for the specified login string, there can only be one >:)
func locateUser(login string) (model.User, error) { func locateUser(login string) (model.User, error) {
var u model.User var u model.User
users, err := getUsers() users, err := getUsers()
if !loginExists(users, login) { if !loginExists(users, login) {
return u, fmt.Errorf("Failed to find login in LDAP database: %v", err) return u, fmt.Errorf("Failed to find login in LDAP database: %v", err)
} }
filter := searchUser(users, false, login, "", "", "", "", "") filter := searchUser(users, false, login, "", "", "", "", "")
if len(filter) != 1 { if len(filter) != 1 {
return u, fmt.Errorf(`More than one user matched the login given. return u, fmt.Errorf(`More than one user matched the login given.
search made: "useradm user show -l %v"`, login) search made: "useradm user show -l %v"`, login)
} }
u = filter[0] u = filter[0]
return u, nil return u, nil
} }
// moves dirs to their respective trash dir
func removeDirs(u model.User) error { func removeDirs(u model.User) error {
err := moveAndChown(u.Homedir, HOME_TRASH, "nobody", "nogroup") err := moveAndChown(u.Homedir, HOME_TRASH, "nobody", "nogroup")
if err != nil { return err } if err != nil { return err }
...@@ -107,21 +115,23 @@ func delUserLDAP(u model.User) error { ...@@ -107,21 +115,23 @@ func delUserLDAP(u model.User) error {
} }
defer l.Close() defer l.Close()
// Remove user from all groups // first remove user from all groups
searchReq := ldap.NewSearchRequest( searchReq := ldap.NewSearchRequest(
"ou=grupos,dc=c3local,dc=com", "ou=grupos,dc=c3local,dc=com",
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 0, 0, false, ldap.NeverDerefAliases, 0, 0, false,
"(memberUid=" + u.UID + ")", // Filter by UID membership "(memberUid=" + u.UID + ")", // filter by UID membership
[]string{"dn"}, []string{"dn"},
nil, nil,
) )
// searches groups for target
groups, err := l.Search(searchReq) groups, err := l.Search(searchReq)
if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { if err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
return fmt.Errorf("Group members search failed: %v", err) return fmt.Errorf("Group members search failed: %v", err)
} }
// removing from groups
for _, entry := range groups.Entries { for _, entry := range groups.Entries {
modReq := ldap.NewModifyRequest(entry.DN, nil) modReq := ldap.NewModifyRequest(entry.DN, nil)
modReq.Delete("memberUid", []string{u.UID}) modReq.Delete("memberUid", []string{u.UID})
...@@ -131,7 +141,7 @@ func delUserLDAP(u model.User) error { ...@@ -131,7 +141,7 @@ func delUserLDAP(u model.User) error {
} }
} }
// Delete user entry // removing user entry
delReq := ldap.NewDelRequest(u.DN, nil) delReq := ldap.NewDelRequest(u.DN, nil)
if err := l.Del(delReq); err != nil && if err := l.Del(delReq); err != nil &&
!ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
...@@ -141,6 +151,7 @@ func delUserLDAP(u model.User) error { ...@@ -141,6 +151,7 @@ func delUserLDAP(u model.User) error {
return nil return nil
} }
// usually not necessary but used in a failed create command
func delKerberosPrincipal(login string) error { func delKerberosPrincipal(login string) error {
cmd := exec.Command("kadmin.local", "-q", fmt.Sprintf("delprinc -force %s", login)) cmd := exec.Command("kadmin.local", "-q", fmt.Sprintf("delprinc -force %s", login))
...@@ -153,26 +164,26 @@ func delKerberosPrincipal(login string) error { ...@@ -153,26 +164,26 @@ func delKerberosPrincipal(login string) error {
} }
func moveAndChown(orig, dest, owner, group string) error { func moveAndChown(orig, dest, owner, group string) error {
// Check if orig exists // check if orig exists
if _, err := os.Stat(orig); err != nil { if _, err := os.Stat(orig); err != nil {
log.Printf("Directory %v not found so not moved\n", orig) log.Printf("Directory %v not found so not moved\n", orig)
return nil return nil
} }
// Construct destination path // construct destination path
destPath := filepath.Join(dest, filepath.Base(orig)) destPath := filepath.Join(dest, filepath.Base(orig))
if _, err := os.Stat(destPath); err == nil { if _, err := os.Stat(destPath); err == nil {
return fmt.Errorf("Directory %v already exists\n", destPath) return fmt.Errorf("Directory %v already exists, can't move\n", destPath)
} }
// Move directory using shell command // move directory
cmd := exec.Command("mv", orig, destPath) cmd := exec.Command("mv", orig, destPath)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("Failed to move dirs: %w\nOutput: %v", err, output) return fmt.Errorf("Failed to move dirs: %w\nOutput: %v", err, output)
} }
// Recursive chown using shell command // recursive chown
cmd = exec.Command("chown", "-R", owner+":"+group, destPath) cmd = exec.Command("chown", "-R", owner+":"+group, destPath)
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("Failed to set owner/group: %w\nOutput: %v", err, output) return fmt.Errorf("Failed to set owner/group: %w\nOutput: %v", err, output)
......
...@@ -19,6 +19,7 @@ var ModCmd = &cobra.Command{ ...@@ -19,6 +19,7 @@ var ModCmd = &cobra.Command{
} }
func init() { func init() {
// possible flags
ModCmd.Flags().StringP("grr", "r", "", "New user GRR") ModCmd.Flags().StringP("grr", "r", "", "New user GRR")
ModCmd.Flags().StringP("shell", "s", "", "New user shell") ModCmd.Flags().StringP("shell", "s", "", "New user shell")
ModCmd.Flags().StringP("login", "l", "", "User login name") ModCmd.Flags().StringP("login", "l", "", "User login name")
...@@ -30,6 +31,7 @@ func init() { ...@@ -30,6 +31,7 @@ func init() {
ModCmd.Flags().StringP("password", "p", "", "New password in plain text (use \"auto\" to autogenerate)") ModCmd.Flags().StringP("password", "p", "", "New password in plain text (use \"auto\" to autogenerate)")
ModCmd.Flags().BoolP ("block", "b", false, "Block the user (changes the password and sets their shell to /bin/false)") ModCmd.Flags().BoolP ("block", "b", false, "Block the user (changes the password and sets their shell to /bin/false)")
// required flags
ModCmd.MarkFlagRequired("login") ModCmd.MarkFlagRequired("login")
ModCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") ModCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt")
...@@ -129,6 +131,7 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error { ...@@ -129,6 +131,7 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error {
return nil return nil
} }
// do NOT include ':' in the charset, it WILL break the command
func genPassword() string { func genPassword() string {
const charset = "@*()=+[];,.?123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ" const charset = "@*()=+[];,.?123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
b := make([]byte, 20) b := make([]byte, 20)
...@@ -141,11 +144,14 @@ func genPassword() string { ...@@ -141,11 +144,14 @@ func genPassword() string {
return string(b) return string(b)
} }
// prints a confirmation prompt, given the operation being performed
func confirmationPrompt(confirm bool, operation string) { func confirmationPrompt(confirm bool, operation string) {
if !confirm { if !confirm {
fmt.Printf("Proceed with user %v? [y/N] ", operation) fmt.Printf("Proceed with user %v? [y/N] ", operation)
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
response, _ := reader.ReadString('\n') response, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(response)) != "y" { if strings.TrimSpace(strings.ToLower(response)) != "y" {
fmt.Fprintln(os.Stderr, "Aborted.") fmt.Fprintln(os.Stderr, "Aborted.")
os.Exit(1) os.Exit(1)
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
) )
const ( const (
NUM_GECOS_FIELDS = 9 NUM_GECOS_FIELDS = 9 // change if going to add new field
PASSWD_PATH = "/etc/ldapscripts/ldapscripts.passwd" PASSWD_PATH = "/etc/ldapscripts/ldapscripts.passwd"
) )
...@@ -24,7 +24,7 @@ var ShowCmd = &cobra.Command{ ...@@ -24,7 +24,7 @@ var ShowCmd = &cobra.Command{
} }
func init() { func init() {
// Possible search fields // possible search fields
ShowCmd.Flags().StringP("grr", "r", "", "Search by user GRR") ShowCmd.Flags().StringP("grr", "r", "", "Search by user GRR")
ShowCmd.Flags().StringP("name", "n", "", "Search by user name (case sensitive)") ShowCmd.Flags().StringP("name", "n", "", "Search by user name (case sensitive)")
ShowCmd.Flags().StringP("login", "l", "", "Search by login") ShowCmd.Flags().StringP("login", "l", "", "Search by login")
...@@ -32,27 +32,34 @@ func init() { ...@@ -32,27 +32,34 @@ func init() {
ShowCmd.Flags().StringP("status", "s", "", "Search by user status (Active/Blocked)") ShowCmd.Flags().StringP("status", "s", "", "Search by user status (Active/Blocked)")
ShowCmd.Flags().StringP("homedir", "d", "", "Search by user homedir") ShowCmd.Flags().StringP("homedir", "d", "", "Search by user homedir")
ShowCmd.Flags().BoolP("ignore", "i", false, "Make the search case-insensitive") // at least one is required!
// At least one is required!
ShowCmd.MarkFlagsOneRequired("grr", "name", "login", "group", "homedir", "status") ShowCmd.MarkFlagsOneRequired("grr", "name", "login", "group", "homedir", "status")
ShowCmd.Flags().BoolP("ignore", "i", false, "Make the search case-insensitive")
} }
func searchUserFunc(cmd *cobra.Command, args []string) error { func searchUserFunc(cmd *cobra.Command, args []string) error {
grr, err := cmd.Flags().GetString("grr") grr, err := getFlagString(cmd,"grr")
if err != nil { return err } if err != nil { return err }
name, err := cmd.Flags().GetString("name")
name, err := getFlagString(cmd,"name")
if err != nil { return err } if err != nil { return err }
login, err := cmd.Flags().GetString("login")
login, err := getFlagString(cmd,"login")
if err != nil { return err } if err != nil { return err }
group, err := cmd.Flags().GetString("group")
group, err := getFlagString(cmd,"group")
if err != nil { return err } if err != nil { return err }
homedir, err := cmd.Flags().GetString("homedir")
homedir, err := getFlagString(cmd,"homedir")
if err != nil { return err } if err != nil { return err }
status, err := cmd.Flags().GetString("status")
status, err := getFlagString(cmd,"status")
if err != nil { return err } if err != nil { return err }
ig, err := cmd.Flags().GetBool("ignore") ig, err := cmd.Flags().GetBool("ignore")
if err != nil { return err } if err != nil { return err }
users, err := getUsers() users, err := getUsers()
if err != nil { return err } if err != nil { return err }
...@@ -75,7 +82,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us ...@@ -75,7 +82,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us
strings.ToLower(substr), strings.ToLower(substr),
) )
} }
return strings.Contains(src, substr) return strings.Contains(src, substr) // normal search
} }
// Search // Search
...@@ -88,7 +95,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us ...@@ -88,7 +95,7 @@ func searchUser(users []model.User, ig bool, l, g, n, r, s, h string) []model.Us
}) })
} }
// Generic function for filtering data types, in this case the model User // generic function for filtering data types, in this case the model User
func Filter[T any](slice []T, predicate func(T) bool) []T { func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T var result []T
for _, v := range slice { for _, v := range slice {
...@@ -100,17 +107,15 @@ func Filter[T any](slice []T, predicate func(T) bool) []T { ...@@ -100,17 +107,15 @@ func Filter[T any](slice []T, predicate func(T) bool) []T {
} }
func getUsers() ([]model.User, error) { func getUsers() ([]model.User, error) {
var users []model.User var users []model.User
// Connect
l, err := connLDAP() l, err := connLDAP()
if err != nil { if err != nil {
return users, err return users, err
} }
defer l.Close() defer l.Close()
// Create the LDAP search request // create the LDAP search request
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"dc=c3local", // search base "dc=c3local", // search base
ldap.ScopeWholeSubtree, // scope ldap.ScopeWholeSubtree, // scope
...@@ -124,21 +129,21 @@ func getUsers() ([]model.User, error) { ...@@ -124,21 +129,21 @@ func getUsers() ([]model.User, error) {
nil, nil,
) )
// Perform the search // perform the search
sr, err := l.Search(searchRequest) sr, err := l.Search(searchRequest)
if err != nil { if err != nil {
err = fmt.Errorf("Failed to fetch users from LDAP: %v", err) err = fmt.Errorf("Failed to fetch users from LDAP: %v", err)
return nil, err return nil, err
} }
// Get all the groups and ids // get all the groups and ids
groups, err := getGroups() groups, err := getGroups()
if err != nil { if err != nil {
err = fmt.Errorf("Failed to fetch groups and gids from LDAP: %v", err) err = fmt.Errorf("Failed to fetch groups and gids from LDAP: %v", err)
return nil, err return nil, err
} }
// Iterate over the search results // iterate over the search results
for _, entry := range sr.Entries { for _, entry := range sr.Entries {
shell := entry.GetAttributeValue("loginShell") shell := entry.GetAttributeValue("loginShell")
...@@ -158,7 +163,7 @@ func getUsers() ([]model.User, error) { ...@@ -158,7 +163,7 @@ func getUsers() ([]model.User, error) {
gidNumber := user.GIDNumber gidNumber := user.GIDNumber
// Safe assignment // safe assignment :)
groupName, exists := groups[gidNumber] groupName, exists := groups[gidNumber]
if !exists { if !exists {
fmt.Printf("WARNING: no group found for GIDNumber %s, user %s. Continuing...", gidNumber, user.UID) fmt.Printf("WARNING: no group found for GIDNumber %s, user %s. Continuing...", gidNumber, user.UID)
...@@ -174,21 +179,21 @@ func getUsers() ([]model.User, error) { ...@@ -174,21 +179,21 @@ func getUsers() ([]model.User, error) {
} }
func connLDAP() (*ldap.Conn, error) { func connLDAP() (*ldap.Conn, error) {
// Connect to the LDAP server // connect to the LDAP server
l, err := ldap.DialURL("ldapi:///") l, err := ldap.DialURL("ldapi:///")
if err != nil { if err != nil {
err = fmt.Errorf("Failed to connect to LDAP: %v", err) err = fmt.Errorf("Failed to connect to LDAP: %v", err)
return nil, err return nil, err
} }
// Get admin credentials // get admin credentials
password, err := getLDAPPassword(PASSWD_PATH) password, err := getLDAPPassword(PASSWD_PATH)
if err != nil { if err != nil {
err = fmt.Errorf("Failed to read LDAP password: %v", err) err = fmt.Errorf("Failed to read LDAP password: %v", err)
return nil, err return nil, err
} }
// Bind using admin credentials // bind using admin credentials
err = l.Bind("cn=admin,dc=c3local", password) err = l.Bind("cn=admin,dc=c3local", password)
if err != nil { if err != nil {
l.Close() l.Close()
...@@ -211,16 +216,16 @@ func getLDAPPassword(path string) (string, error) { ...@@ -211,16 +216,16 @@ func getLDAPPassword(path string) (string, error) {
return password, nil return password, nil
} }
// Campos do GECOS: // Gecos Fields:
// 0. Nome completo do titular da conta // 0. users full name
// 1. GRR do aluno // 1. users grr
// 2. Professor responsavel // 2. responsible professor
// 3. Curso/minicurso do usuario (ate para temporarios) // 3. users course/minicourse (even temp)
// 4. Validade da conta (data maxima para delecao) // 4. account expiry date (may be used for deletion)
// 5. Status da conta (Blocked/Active) // 5. account status (Blocked/Active)
// 6. Tipo de login (ini, first, last) // 6. login type (ini, first or last)
// 7. Webdir do usuario // 7. users webdir
// 8. Nobackup do usuario // 8. users nobackup dir
// 9. to be continued... // 9. to be continued...
func parseGecos(user model.User) model.User { func parseGecos(user model.User) model.User {
result := [NUM_GECOS_FIELDS]string{0: "_"} result := [NUM_GECOS_FIELDS]string{0: "_"}
...@@ -238,10 +243,11 @@ func parseGecos(user model.User) model.User { ...@@ -238,10 +243,11 @@ func parseGecos(user model.User) model.User {
user.GRR = result[1] user.GRR = result[1]
user.Resp = result[2] user.Resp = result[2]
user.Course = result[3] user.Course = result[3]
user.Expiry = result[4]
// only set with gecos if empty
if user.Status == "_"{ if user.Status == "_"{
user.Status = result[5] user.Status = result[5]
} }
user.Expiry = result[4]
user.Ltype = result[6] user.Ltype = result[6]
user.Webdir = result[7] user.Webdir = result[7]
user.Nobackup = result[8] user.Nobackup = result[8]
...@@ -249,9 +255,7 @@ func parseGecos(user model.User) model.User { ...@@ -249,9 +255,7 @@ func parseGecos(user model.User) model.User {
return user return user
} }
func getGroups() (map[string]string, error) { func getGroups() (map[string]string, error) {
groupMap := make(map[string]string) groupMap := make(map[string]string)
l, err := connLDAP() l, err := connLDAP()
...@@ -260,22 +264,24 @@ func getGroups() (map[string]string, error) { ...@@ -260,22 +264,24 @@ func getGroups() (map[string]string, error) {
} }
defer l.Close() defer l.Close()
// build search
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"dc=c3local", "dc=c3local",
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, ldap.NeverDerefAliases,
0, 0, 0, 0, false,
false,
"(&(objectClass=posixGroup))", "(&(objectClass=posixGroup))",
[]string{"gidNumber", "cn"}, []string{"gidNumber", "cn"},
nil, nil,
) )
// search
sr, err := l.Search(searchRequest) sr, err := l.Search(searchRequest)
if err != nil { if err != nil {
return nil, fmt.Errorf("LDAP search failed: %w", err) return nil, fmt.Errorf("LDAP search failed: %w", err)
} }
// arrange result into a map string->string
for _, entry := range sr.Entries { for _, entry := range sr.Entries {
gid := entry.GetAttributeValue("gidNumber") gid := entry.GetAttributeValue("gidNumber")
cn := entry.GetAttributeValue("cn") cn := entry.GetAttributeValue("cn")
...@@ -288,7 +294,6 @@ func getGroups() (map[string]string, error) { ...@@ -288,7 +294,6 @@ func getGroups() (map[string]string, error) {
} }
func getUIDs() ([]int, error) { func getUIDs() ([]int, error) {
var uidNumbers []int var uidNumbers []int
l, err := connLDAP() l, err := connLDAP()
...@@ -297,22 +302,24 @@ func getUIDs() ([]int, error) { ...@@ -297,22 +302,24 @@ func getUIDs() ([]int, error) {
} }
defer l.Close() defer l.Close()
// build search
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
"dc=c3local", "dc=c3local",
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, ldap.NeverDerefAliases,
0, 0, 0, 0, false,
false,
"(&(objectClass=posixAccount))", "(&(objectClass=posixAccount))",
[]string{"uidNumber"}, []string{"uidNumber"},
nil, nil,
) )
// search
sr, err := l.Search(searchRequest) sr, err := l.Search(searchRequest)
if err != nil { if err != nil {
return nil, fmt.Errorf("LDAP search failed: %w", err) return nil, fmt.Errorf("LDAP search failed: %w", err)
} }
// arrange result into a array of integers
for _, entry := range sr.Entries { for _, entry := range sr.Entries {
uidStr := entry.GetAttributeValue("uidNumber") uidStr := entry.GetAttributeValue("uidNumber")
if uidStr != "" { if uidStr != "" {
...@@ -324,6 +331,7 @@ func getUIDs() ([]int, error) { ...@@ -324,6 +331,7 @@ func getUIDs() ([]int, error) {
} }
} }
// sort for ease of use :)
sort.Ints(uidNumbers) sort.Ints(uidNumbers)
return uidNumbers, nil return uidNumbers, nil
...@@ -333,11 +341,13 @@ func loginExists(users []model.User, login string) bool { ...@@ -333,11 +341,13 @@ func loginExists(users []model.User, login string) bool {
return exists(users, func(u model.User) bool { return u.UID == login }) return exists(users, func(u model.User) bool { return u.UID == login })
} }
// accepts GRRs in the format "GRRXXXXXXXX" aswell as 8 digit number
func grrExists(users []model.User, grr string) bool { func grrExists(users []model.User, grr string) bool {
return exists(users, func(u model.User) bool { return u.GRR == grr }) || return exists(users, func(u model.User) bool { return u.GRR == grr }) ||
exists(users, func(u model.User) bool { return u.GRR == "GRR" + grr }) exists(users, func(u model.User) bool { return u.GRR == "GRR" + grr })
} }
// generic function for check existence of data types
func exists[T any](slice []T, predicate func(T) bool) bool { func exists[T any](slice []T, predicate func(T) bool) bool {
for _, item := range slice { for _, item := range slice {
if predicate(item) { if predicate(item) {
......
...@@ -41,6 +41,7 @@ func (u *User) ToString() string { ...@@ -41,6 +41,7 @@ func (u *User) ToString() string {
u.Nobackup, u.Status, u.Resp, u.Course, u.Expiry) u.Nobackup, u.Status, u.Resp, u.Course, u.Expiry)
} }
// useful for debugging :)
func (u *User) FullToString() string { func (u *User) FullToString() string {
return fmt.Sprintf(`User: return fmt.Sprintf(`User:
DN: %s DN: %s
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment