From 987f21d10dc723180aeff4579bfae717924984b6 Mon Sep 17 00:00:00 2001
From: tss24 <tss24@inf.ufpr.br>
Date: Fri, 21 Mar 2025 11:14:58 -0300
Subject: [PATCH] Add user blocking/unblocking and finish bulk prototype

---
 cmd/bulk.go                                   | 170 ++++++++++++++++++
 cmd/root.go                                   |   1 +
 cmd/user.go                                   |   6 +-
 cmd/user/block.go                             |  36 ++++
 cmd/user/bulk.go                              |  52 +++++-
 cmd/user/create.go                            |  81 ++-------
 cmd/user/mod.go                               |   9 +-
 cmd/user/temp.go                              |  16 +-
 cmd/user/unblock.go                           |  41 +++++
 model/krb.go                                  |  11 ++
 model/ltype.go                                |  13 ++
 model/opts.go                                 |   4 +-
 model/user.go                                 | 167 ++++++++++++++++-
 utils/utils.go                                |  12 +-
 .../validation.go => validate/validate.go     |  12 +-
 15 files changed, 518 insertions(+), 113 deletions(-)
 create mode 100644 cmd/bulk.go
 create mode 100644 cmd/user/block.go
 create mode 100644 cmd/user/unblock.go
 rename cmd/user/validation.go => validate/validate.go (83%)

diff --git a/cmd/bulk.go b/cmd/bulk.go
new file mode 100644
index 0000000..24c26e1
--- /dev/null
+++ b/cmd/bulk.go
@@ -0,0 +1,170 @@
+package cmd
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/spf13/cobra"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/model"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/utils"
+	//"gitlab.c3sl.ufpr.br/tss24/useradm/validate"
+)
+
+var (
+	// if you start asking yourself wtf is the difference between fis and fisica,
+	// im pretty sure it is that fisica has profs and/or MSc/Doctorate...
+	supp_groups = [...]string{"ppginf", "espinf", "temp",
+		"bcc", "ibm", "est", "fis"}
+)
+
+var bulkCmd = &cobra.Command{
+	Use:   "bulk [group] [path]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Subcommand to create a lot of accounts",
+	Long: `
+  This subcommand is used when you have a file with 
+  a lot of names and want to create the accounts. 
+  The next argument is the group of the users, so 
+  please take care when choosing it. 
+
+  Be aware the results will be printed to the screen, 
+  so if you want to save it, redirect the command's 
+  output to a file!
+
+  Supported groups are:
+  bcc, ibm, est, ppginf, espinf, temp, fis 
+
+  If your group is not here and you want to create a
+  lot of user similarly, contact root@c3sl.ufpr.br
+  for us to update the script.`,
+	Example: `    
+  useradm bulk bcc path/to/file                          # base command
+  useradm bulk ibm path/to/file -e 12.03.25              # setting expiry  
+  useradm bulk ppginf path/to/file -r "Prof John Doe"    # setting resp  
+  useradm bulk est path/to/file > out-file.txt           # redirecting to file`,
+	RunE: bulkCreate,
+}
+
+func init() {
+	bulkCmd.Flags().StringP("resp", "r", "_", "Person responsible for the accounts")
+	bulkCmd.Flags().StringP("expiry", "e", "_", "Expiry date of the accounts, format dd.mm.yy")
+}
+
+func bulkCreate(cmd *cobra.Command, args []string) error {
+	var opts model.Opts
+
+	group := args[0]
+	path := args[1]
+
+	err := isGroupSupported(group)
+	if err != nil {
+		return err
+	}
+
+	if !utils.PathExists(path) {
+		return fmt.Errorf("Path %v not found", path)
+	}
+
+	err = opts.RetrieveOpts(cmd)
+	if err != nil {
+		return err
+	}
+
+	accounts, err := extractModelsFile(opts, group, path)
+	if err != nil {
+		return err
+	}
+
+	l, err := model.ConnLDAP()
+	if err != nil {
+		return err
+	}
+	defer l.Close()
+
+	users, err := model.GetAllUsersLDAP(l)
+	if err != nil {
+		return err
+	}
+
+	for _, u := range accounts {
+
+		err = u.GenMissingFields(false)
+		if err != nil {
+			return err
+		}
+
+		if !model.LoginExists(users, u.UID) {
+			err = u.Create(l)
+			if err != nil {
+				return err
+			}
+		} else {
+			fmt.Printf(`
+First login generation alternative for "%v" is already taken!
+User was NOT created!
+Please check if the user exists with:
+                
+  useradm user show -e -l %v
+  useradm user show -i -n "%v"
+
+If it really is a new user, use one of these: 
+
+  useradm user create -n "%v" -g %v -e %v -i %v -t %v -r %v -w     # with webdir
+  useradm user create -n "%v" -g %v -e %v -i %v -t %v -r %v        # no webdir
+`, u.UID, u.UID, u.Name, u.Name, u.GID, u.Expiry, u.Resp, u.Ltype.SmallString(),
+				u.GRR, u.Name, u.GID, u.Expiry, u.Resp, u.Ltype.SmallString(), u.GRR)
+		}
+
+	}
+
+	return nil
+}
+
+func isGroupSupported(group string) error {
+	err := fmt.Errorf("Group %v is not supported, see help", group)
+	for _, g := range supp_groups {
+		if g == group {
+			err = nil
+			break
+		}
+	}
+	return err
+}
+
+func extractModelsFile(opts model.Opts, group, path string) ([]model.User, error) {
+	var users []model.User
+
+	file, err := os.Open(path)
+	if err != nil {
+		return users, fmt.Errorf("Error opening file %v", err)
+	}
+	defer file.Close()
+
+	var ltype model.LoginType
+	if group == "ppginf" || group == "espinf" {
+		ltype = model.LastName
+	} else {
+		ltype = model.Initials
+	}
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		var u model.User
+		line := scanner.Text()
+		parts := strings.Split(line, ":")
+		u.Name = parts[0]
+		u.GRR = parts[1]
+		u.GID = group
+		u.Ltype = ltype
+		u.Resp = opts.Resp
+		u.Expiry = opts.Expiry
+		users = append(users, u)
+	}
+
+	if err := scanner.Err(); err != nil {
+		fmt.Println("Error reading file:", err)
+	}
+	return users, nil
+}
diff --git a/cmd/root.go b/cmd/root.go
index c8d2f2d..475101c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -30,4 +30,5 @@ func Execute() {
 func init() {
 	rootCmd.AddCommand(userCmd)
 	rootCmd.AddCommand(groupCmd)
+	rootCmd.AddCommand(bulkCmd)
 }
diff --git a/cmd/user.go b/cmd/user.go
index c2347b0..83497f4 100644
--- a/cmd/user.go
+++ b/cmd/user.go
@@ -8,15 +8,17 @@ import (
 var userCmd = &cobra.Command{
 	Use:   "user",
 	Short: "User subcommand",
-	Long: `Subcommand for managing users in general.`,
+	Long:  `Subcommand for managing users in general.`,
 }
 
 func init() {
 	userCmd.AddCommand(user.ModCmd)
 	userCmd.AddCommand(user.ShowCmd)
 	userCmd.AddCommand(user.TempCmd)
-	userCmd.AddCommand(user.BulkCmd)
+	//userCmd.AddCommand(user.BulkCmd)
 	userCmd.AddCommand(user.ResetCmd)
 	userCmd.AddCommand(user.CreateCmd)
 	userCmd.AddCommand(user.RemoveCmd)
+	userCmd.AddCommand(user.BlockCmd)
+	userCmd.AddCommand(user.UnblockCmd)
 }
diff --git a/cmd/user/block.go b/cmd/user/block.go
new file mode 100644
index 0000000..6255f13
--- /dev/null
+++ b/cmd/user/block.go
@@ -0,0 +1,36 @@
+package user
+
+import (
+	"fmt"
+
+	"github.com/spf13/cobra"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/model"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/utils"
+)
+
+var BlockCmd = &cobra.Command{
+	Use:   "block [username]",
+	Short: "Block a user",
+	Args:  cobra.ExactArgs(1),
+	RunE:  BlockUserCmd,
+}
+
+func BlockUserCmd(cmd *cobra.Command, args []string) error {
+	l, err := model.ConnLDAP()
+	if err != nil {
+		return err
+	}
+	defer l.Close()
+
+	login := args[0]
+	u, err := model.Locate(l, login)
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("%v\n", u.FullToString())
+
+	utils.ConfirmationPrompt(false, "blocking")
+	u.Block()
+	return nil
+}
diff --git a/cmd/user/bulk.go b/cmd/user/bulk.go
index ed92d59..6c35444 100644
--- a/cmd/user/bulk.go
+++ b/cmd/user/bulk.go
@@ -1,7 +1,10 @@
 package user
 
 import (
+	"bufio"
 	"fmt"
+	"os"
+	"strings"
 
 	"github.com/spf13/cobra"
 	"gitlab.c3sl.ufpr.br/tss24/useradm/model"
@@ -43,26 +46,57 @@ func bulkCreate(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	if !utils.PathExists(opts.Path) {
-		return fmt.Errorf("Path: \"%v\": no such file", opts.Path)
-	}
-
-	accounts, err = extractModelsFile(opts.Path)
+	accounts, err = extractModelsFile(opts.Path, opts.GID)
 	if err != nil {
 		return err
 	}
 
-	for i := range accounts {
-		err = accounts[i].Create(l)
+	for _, account := range accounts {
+
+		err = account.GenMissingFields(false)
 		if err != nil {
 			return err
 		}
+		//	if model.LoginExists(account.UID) {
+		//		return
+		//  }
+
+		err = account.Create(l)
+		if err != nil {
+			return err
+		}
+
 	}
 
 	return nil
 }
 
-func extractModelsFile(path string) ([]model.User, error) {
-	// TODO
+func extractModelsFile(path, group string) ([]model.User, error) {
+	var users []model.User
+
+	if !utils.PathExists(path) {
+		return users, fmt.Errorf("Path: \"%v\": no such file", path)
+	}
+
+	file, err := os.Open(path)
+	if err != nil {
+		return users, fmt.Errorf("Error opening file %v", err)
+	}
+	defer file.Close()
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		var u model.User
+		line := scanner.Text()
+		parts := strings.Split(line, ":")
+		u.Name = parts[0]
+		u.GRR = parts[1]
+		u.GID = group
+		users = append(users, u)
+	}
+
+	if err := scanner.Err(); err != nil {
+		fmt.Println("Error reading file:", err)
+	}
 	return nil, nil
 }
diff --git a/cmd/user/create.go b/cmd/user/create.go
index 540dc54..100d8ed 100644
--- a/cmd/user/create.go
+++ b/cmd/user/create.go
@@ -7,6 +7,7 @@ import (
 	"github.com/spf13/cobra"
 	"gitlab.c3sl.ufpr.br/tss24/useradm/model"
 	"gitlab.c3sl.ufpr.br/tss24/useradm/utils"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/validate"
 )
 
 var CreateCmd = &cobra.Command{
@@ -20,7 +21,6 @@ func init() {
 	// FIXME: maybe leave less flags for user input
 	CreateCmd.Flags().StringP("grr", "r", "_", "User GRR, required for ini type")
 	CreateCmd.Flags().StringP("type", "t", "ini", "Type of auto-generated login: ini, first or last")
-	CreateCmd.Flags().StringP("web", "w", "", "Full path to webdir, /home/html/inf/login if empty")
 	CreateCmd.Flags().StringP("name", "n", "_", "User full name, required, use quotes for spaces")
 	CreateCmd.Flags().StringP("login", "l", "", "User login name, auto-generated if empty")
 	CreateCmd.Flags().StringP("group", "g", "", "User base group, required")
@@ -31,6 +31,7 @@ func init() {
 	CreateCmd.Flags().StringP("resp", "i", "_", "Person responsible for the account")
 	CreateCmd.Flags().StringP("expiry", "e", "_", "Expiry date in format dd.mm.yy")
 	CreateCmd.Flags().StringP("nobkp", "b", "", "User nobackup directory path")
+	CreateCmd.Flags().BoolP("web", "w", false, "Generate webdir")
 
 	// required flags
 	CreateCmd.MarkFlagRequired("name")
@@ -57,10 +58,6 @@ func createUserCmd(cmd *cobra.Command, args []string) error {
 	//fmt.Printf("%v\n	Passwd:  %v\n\n", usr.ToString(), usr.Password)
 	utils.ConfirmationPrompt(confirm, "creation")
 
-	if usr.Password == "[auto-generate]" {
-		usr.Password = utils.GenPassword()
-	}
-
 	err = usr.Create(l)
 	if err != nil {
 		return err
@@ -68,15 +65,9 @@ func createUserCmd(cmd *cobra.Command, args []string) error {
 
 	err = utils.ClearCache()
 	if err != nil {
-		fmt.Printf("Failed to reload cache, changes might take a while\nError: %v", err)
+		fmt.Printf("Failed to reload cache, changes might take a while\nError: %v\n", err)
 	}
 
-	fmt.Println("User created!")
-	fmt.Printf("\nUser Login:     %v", usr.UID)
-	fmt.Printf("\nUser Password:  %v\n\n", usr.Password)
-	fmt.Printf("%v:%v:%v:%v:%v:%v:%v:%v:%v:\n", usr.UID, usr.Password, usr.GID,
-		usr.Name, usr.Gecos, usr.GRR, usr.Shell, usr.Homedir, usr.Webdir)
-
 	return nil
 }
 
@@ -94,16 +85,6 @@ func createNewUserModel(cmd *cobra.Command, l *ldap.Conn) (model.User, bool, err
 		return u, false, err
 	}
 
-	users, err := model.GetAllUsersLDAP(l)
-	if err != nil {
-		return u, false, err
-	}
-
-	groups, err := model.GetAllGroupsLDAP(l)
-	if err != nil {
-		return u, false, err
-	}
-
 	if opts.Status == "Blocked" {
 		opts.Shell = "/bin/false"
 	}
@@ -121,56 +102,14 @@ func createNewUserModel(cmd *cobra.Command, l *ldap.Conn) (model.User, bool, err
 		Shell:    opts.Shell,
 		Status:   opts.Status,
 		Expiry:   opts.Expiry,
-		Webdir:   opts.Webdir,
 		Homedir:  opts.Homedir,
 		Nobackup: opts.Nobkp,
-		Password: utils.IfThenElse(opts.Passwd != "", opts.Passwd, "[auto-generate]"),
+		Password: opts.Passwd,
 	}
 
 	u.Ltype.Parse(opts.Ltype)
 
-	if opts.UID == "" {
-		err = u.GenUniqueUID(users)
-		if err != nil {
-			return u, false, err
-		}
-	}
-
-	if opts.Homedir == "" {
-		u.Homedir, err = utils.GenDirPath("/home", u.GID, u.UID, u.Homedir)
-		if err != nil {
-			return u, false, err
-		}
-	}
-
-	if opts.Nobkp == "" {
-		u.Nobackup, err = utils.GenDirPath("/nobackup", u.GID, u.UID, u.Nobackup)
-		if err != nil {
-			return u, false, err
-		}
-	}
-
-	if opts.Webdir == "" {
-		u.Webdir, err = utils.GenDirPath("/home/html/inf", "", u.UID, u.Webdir)
-		if err != nil {
-			return u, false, err
-		}
-	}
-
-	// get a new UIDNumber
-	err = u.GetNewUIDNumber(l)
-	if err != nil {
-		return u, false, fmt.Errorf("failed to generate new UIDNumber for user: %v", err)
-	}
-
-	// assign GIDNumber by traversing the groups
-	u.GIDNumber, err = utils.GetGIDNumFromGID(groups, u.GID)
-	if err != nil {
-		return u, false, err
-	}
-
-	u.GenGecos()
-	u.SetDN(u.UID)
+	u.GenMissingFields(true)
 
 	return u, opts.Confirm, nil
 }
@@ -193,29 +132,29 @@ func validateInputs(opts model.Opts) error {
 		return err
 	}
 
-	err = validateGID(groups, opts.GID)
+	err = validate.GID(groups, opts.GID)
 	if err != nil {
 		return err
 	}
 
-	err = validateGRR(users, opts.GRR)
+	err = validate.GRR(users, opts.GRR)
 	if err != nil {
 		return err
 	}
 
-	err = validateExpiry(opts.Expiry)
+	err = validate.Expiry(opts.Expiry)
 	if err != nil {
 		return err
 	}
 
-	err = validateStatus(opts.Status)
+	err = validate.Status(opts.Status)
 	if err != nil {
 		return err
 	}
 
 	// it’s OK if UID is empty here, we generate it later :)
 	if opts.UID != "" {
-		err := validateUID(users, opts.UID)
+		err := validate.UID(users, opts.UID)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/user/mod.go b/cmd/user/mod.go
index 4b9e9f3..9b9b1d7 100644
--- a/cmd/user/mod.go
+++ b/cmd/user/mod.go
@@ -10,6 +10,7 @@ import (
 	"github.com/spf13/cobra"
 	"gitlab.c3sl.ufpr.br/tss24/useradm/model"
 	"gitlab.c3sl.ufpr.br/tss24/useradm/utils"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/validate"
 	"gopkg.in/yaml.v3"
 )
 
@@ -78,23 +79,23 @@ func modUserCmd(cmd *cobra.Command, args []string) error {
 	changes, err := promptUserYaml(state)
 
 	if changes.GRR != curr.GRR {
-		err = validateGRR(users, changes.GRR)
+		err = validate.GRR(users, changes.GRR)
 		if err != nil {
 			return err
 		}
 	}
 
-	err = validateGID(groups, changes.Group)
+	err = validate.GID(groups, changes.Group)
 	if err != nil {
 		return err
 	}
 
-	err = validateStatus(changes.Status)
+	err = validate.Status(changes.Status)
 	if err != nil {
 		return err
 	}
 
-	err = validateExpiry(changes.Expiry)
+	err = validate.Expiry(changes.Expiry)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/user/temp.go b/cmd/user/temp.go
index 7170e14..ff46b6d 100644
--- a/cmd/user/temp.go
+++ b/cmd/user/temp.go
@@ -9,6 +9,7 @@ import (
 	"github.com/spf13/cobra"
 	"gitlab.c3sl.ufpr.br/tss24/useradm/model"
 	"gitlab.c3sl.ufpr.br/tss24/useradm/utils"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/validate"
 )
 
 var TempCmd = &cobra.Command{
@@ -62,7 +63,7 @@ func tempCreate(cmd *cobra.Command, args []string) error {
 		}
 	}
 
-	err = validateGID(groups, opts.GID)
+	err = validate.GID(groups, opts.GID)
 	if err != nil {
 		return err
 	}
@@ -81,14 +82,15 @@ func tempCreate(cmd *cobra.Command, args []string) error {
 		Password: opts.Passwd + "#",
 	}
 
-	base.Homedir, err = utils.GenDirPath("/home", base.GID, base.UID, "")
-	if err != nil {
-		return err
+	var exists bool
+	base.Homedir, exists = utils.GenDirPath("/home", base.GID, base.UID)
+	if exists {
+		return fmt.Errorf("Path %v already exists", base.Homedir)
 	}
 
-	base.Nobackup, err = utils.GenDirPath("/nobackup", base.GID, base.UID, "")
-	if err != nil {
-		return err
+	base.Nobackup, exists = utils.GenDirPath("/nobackup", base.GID, base.UID)
+	if exists {
+		return fmt.Errorf("Path %v already exists", base.Nobackup)
 	}
 
 	i := 1
diff --git a/cmd/user/unblock.go b/cmd/user/unblock.go
new file mode 100644
index 0000000..56ec9b6
--- /dev/null
+++ b/cmd/user/unblock.go
@@ -0,0 +1,41 @@
+package user
+
+import (
+	"github.com/spf13/cobra"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/model"
+	"gitlab.c3sl.ufpr.br/tss24/useradm/utils"
+)
+
+var UnblockCmd = &cobra.Command{
+	Use:   "unblock [username]",
+	Short: "Unblock a user",
+	Args:  cobra.ExactArgs(1),
+	RunE:  UnblockUserCmd,
+}
+
+func init() {
+	UnblockCmd.Flags().StringP("passwd", "p", "_", "User's new password")
+}
+
+func UnblockUserCmd(cmd *cobra.Command, args []string) error {
+	pass, err := cmd.Flags().GetString("passwd")
+	if err != nil {
+		return err
+	}
+
+	l, err := model.ConnLDAP()
+	if err != nil {
+		return err
+	}
+	defer l.Close()
+
+	login := args[0]
+	u, err := model.Locate(l, login)
+	if err != nil {
+		return err
+	}
+
+	utils.ConfirmationPrompt(false, "unblocking")
+	u.Unblock(pass)
+	return nil
+}
diff --git a/model/krb.go b/model/krb.go
index 8f90546..d461d8a 100644
--- a/model/krb.go
+++ b/model/krb.go
@@ -64,3 +64,14 @@ func DelKRBPrincipal(login string) error {
 
 	return nil
 }
+
+func KRBRandKey(login string) error {
+	cmd := exec.Command("kadmin.local", "-q", fmt.Sprintf("cpw -randkey %s", login))
+
+	output, err := cmd.CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("Fail to set randkey in Kerberos: %v\nOutput: %s", err, output)
+	}
+
+	return nil
+}
diff --git a/model/ltype.go b/model/ltype.go
index 4fbbc0f..02df3c1 100644
--- a/model/ltype.go
+++ b/model/ltype.go
@@ -38,3 +38,16 @@ func (lt *LoginType) Parse(s string) {
 		*lt = LoginTypeUnknown
 	}
 }
+
+func (lt LoginType) SmallString() string {
+	switch lt {
+	case Initials:
+		return "ini"
+	case FirstName:
+		return "first"
+	case LastName:
+		return "last"
+	default:
+		return "unknown"
+	}
+}
diff --git a/model/opts.go b/model/opts.go
index a89fdce..b399d8b 100644
--- a/model/opts.go
+++ b/model/opts.go
@@ -17,10 +17,10 @@ type Opts struct {
 	Ltype   string
 	Shell   string
 	Passwd  string
-	Webdir  string
 	Status  string
 	Expiry  string
 	Homedir string
+	Webdir  bool
 	Ignore  bool
 	Exact   bool
 	Block   bool
@@ -94,7 +94,7 @@ func (o *Opts) RetrieveOpts(cmd *cobra.Command) error {
 		return err
 	}
 
-	o.Webdir, err = getFlagString(cmd, "web")
+	o.Webdir, err = getFlagBool(cmd, "web")
 	if err != nil {
 		return err
 	}
diff --git a/model/user.go b/model/user.go
index 45490f6..35c932f 100644
--- a/model/user.go
+++ b/model/user.go
@@ -28,6 +28,7 @@ var (
 	NO_BKP_TRASH = "/nobackup/contas_removidas/" + ANO
 	HOME_TRASH   = "/home/contas_removidas/" + ANO
 	WEB_TRASH    = "/home/contas_removidas/html/" + ANO
+	GID_HAS_WEB  = [...]string{"bcc", "ibm", "ppginf", "c3sl", "prof", "especial"}
 )
 
 type User struct {
@@ -116,6 +117,8 @@ func (u *User) Create(l *ldap.Conn) error {
 		return err
 	}
 
+	fmt.Printf("%v:%v:%v:%v:%v:%v:%v:%v:%v:%v:add-ok\n", u.UID, u.Password, u.GID,
+		u.Name, u.Ltype.String(), u.GRR, u.Shell, u.Homedir, u.Webdir, u.Expiry)
 	success = true
 	return nil
 }
@@ -340,9 +343,11 @@ func (u *User) DirsToTrash() error {
 		return err
 	}
 
-	err = utils.MoveAndChown(u.Webdir, WEB_TRASH, "nobody", "nogroup")
-	if err != nil {
-		return err
+	if u.Webdir != "_" {
+		err = utils.MoveAndChown(u.Webdir, WEB_TRASH, "nobody", "nogroup")
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil
@@ -470,3 +475,159 @@ func (u *User) CreateWeb() error {
 	success = true
 	return nil
 }
+
+// needs the name, group, ltype.
+// will derivate the rest of the information.
+// if it can't infer the field, will set to "_"
+func (u *User) GenMissingFields(force bool) error {
+	var err error
+
+	l, err := ConnLDAP()
+	if err != nil {
+		return err
+	}
+	defer l.Close()
+
+	if u.UID == "" {
+		if force {
+			users, err := GetAllUsersLDAP(l)
+			if err != nil {
+				return err
+			}
+			u.GenUniqueUID(users)
+		} else {
+			u.genLogin(0)
+		}
+	}
+
+	if u.DN == "" {
+		u.SetDN(u.UID)
+	}
+
+	if u.GRR == "" {
+		u.GRR = "_"
+	}
+
+	if u.Expiry == "" {
+		u.Expiry = "_"
+	}
+
+	if u.Status == "" {
+		u.Status = "Active"
+	}
+
+	if u.Shell == "" {
+		u.Shell = "/bin/bash"
+	}
+
+	groups, err := GetAllGroupsLDAP(l)
+	if err != nil {
+		return err
+	}
+
+	if u.Password == "" {
+		u.Password = utils.GenPassword()
+	}
+
+	u.GIDNumber, err = utils.GetGIDNumFromGID(groups, u.GID)
+	if err != nil {
+		return err
+	}
+
+	if u.Homedir == "" {
+		u.Homedir, _ = utils.GenDirPath("/home", u.GID, u.UID)
+	}
+
+	if u.Nobackup == "" {
+		u.Nobackup, _ = utils.GenDirPath("/nobackup", u.GID, u.UID)
+	}
+
+	u.Webdir = "_"
+	for _, e := range GID_HAS_WEB {
+		if e == u.GID {
+			u.Webdir, _ = utils.GenDirPath("/home/html/inf", "", u.UID)
+			break
+		}
+	}
+
+	if u.UIDNumber == "" {
+		err = u.GetNewUIDNumber(l)
+		if err != nil {
+			return fmt.Errorf("Failed to generate new UIDNumber for user: %v", err)
+		}
+	}
+
+	u.GenGecos()
+
+	return nil
+}
+
+func (u *User) Block() error {
+	err := KRBRandKey(u.UID)
+	if err != nil {
+		return err
+	}
+	cmd := exec.Command("chmod", "0000", u.Homedir)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("Failed to set permissions: %w", err)
+	}
+
+	cmd = exec.Command("chmod", "0000", u.Nobackup)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("Failed to set permissions: %w", err)
+	}
+
+	l, err := ConnLDAP()
+	if err != nil {
+		return err
+	}
+	defer l.Close()
+
+	mod := ldap.NewModifyRequest(u.DN, nil)
+	mod.Replace("loginShell", []string{"/bin/false"})
+
+	if err := l.Modify(mod); err != nil {
+		return fmt.Errorf("failed to update loginShell for %s: %w", u.UID, err)
+	}
+
+	u.Status = "Blocked"
+	return nil
+}
+
+func (u *User) Unblock(pass string) error {
+	cmd := exec.Command("chmod", "0700", u.Homedir)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("Failed to set permissions: %w", err)
+	}
+
+	cmd = exec.Command("chmod", "0700", u.Nobackup)
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("Failed to set permissions: %w", err)
+	}
+
+	l, err := ConnLDAP()
+	if err != nil {
+		return err
+	}
+	defer l.Close()
+
+	mod := ldap.NewModifyRequest(u.DN, nil)
+	mod.Replace("loginShell", []string{"/bin/bash"})
+
+	if err := l.Modify(mod); err != nil {
+		return fmt.Errorf("Failed to update loginShell for %s: %w", u.UID, err)
+	}
+
+	if pass == "_" {
+		pass = utils.GenPassword()
+	}
+
+	err = ModKRBPassword(u.UID, pass)
+	if err != nil {
+		return err
+	}
+
+	u.Status = "Active"
+	fmt.Printf("New Password: %v\n", pass)
+	return nil
+}
diff --git a/utils/utils.go b/utils/utils.go
index 81e340d..cc0fa14 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -70,17 +70,11 @@ func GetGIDNumFromGID(groups map[string]string, GID string) (string, error) {
 	return "", fmt.Errorf("Couldn't find group GIDNumber")
 }
 
-func GenDirPath(base, group, login, input string) (string, error) {
-	if input != "" {
-		return input, nil
-	}
+// returns a path and if it exists
+func GenDirPath(base, group, login string) (string, bool) {
 	p := filepath.Join(base, group, login)
 
-	if PathExists(p) {
-		return p, fmt.Errorf("Path %v already exists: %v, %v, %v, %v", p, base, group, login, input)
-	}
-
-	return p, nil
+	return p, PathExists(p)
 }
 
 func PathExists(path string) bool {
diff --git a/cmd/user/validation.go b/validate/validate.go
similarity index 83%
rename from cmd/user/validation.go
rename to validate/validate.go
index ec2c952..67f2527 100644
--- a/cmd/user/validation.go
+++ b/validate/validate.go
@@ -1,4 +1,4 @@
-package user
+package validate
 
 import (
 	"fmt"
@@ -9,7 +9,7 @@ import (
 	"gitlab.c3sl.ufpr.br/tss24/useradm/utils"
 )
 
-func validateExpiry(expiry string) error {
+func Expiry(expiry string) error {
 	if expiry == "_" {
 		return nil
 	}
@@ -23,7 +23,7 @@ func validateExpiry(expiry string) error {
 }
 
 // TODO: change this check
-func validateStatus(status string) error {
+func Status(status string) error {
 	if status != "Blocked" && status != "Active" {
 		err := fmt.Errorf("User status can only be \"Active\" or \"Blocked\"")
 		return err
@@ -31,7 +31,7 @@ func validateStatus(status string) error {
 	return nil
 }
 
-func validateGRR(users []model.User, grr string) error {
+func GRR(users []model.User, grr string) error {
 	// OK if empty, only "ini" login type requires it and we check :)
 	if grr == "_" {
 		return nil
@@ -52,7 +52,7 @@ Note: To search for the account use "useradm user show -r %s"`, grr)
 	return nil
 }
 
-func validateGID(groups map[string]string, group string) error {
+func GID(groups map[string]string, group string) error {
 	var err error
 
 	for _, value := range groups {
@@ -64,7 +64,7 @@ func validateGID(groups map[string]string, group string) error {
 	return err
 }
 
-func validateUID(users []model.User, login string) error {
+func UID(users []model.User, login string) error {
 	res := model.Search(users, false, true, login, "", "", "", "", "")
 	if len(res) != 0 {
 		return fmt.Errorf(`The informed Login already exists in LDAP database
-- 
GitLab