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

add reset subcommand and finish mod prototype

parent 537b310a
No related branches found
No related tags found
No related merge requests found
...@@ -18,4 +18,5 @@ func init() { ...@@ -18,4 +18,5 @@ func init() {
userCmd.AddCommand(user.DeleteUserCmd) userCmd.AddCommand(user.DeleteUserCmd)
userCmd.AddCommand(user.ModCmd) userCmd.AddCommand(user.ModCmd)
userCmd.AddCommand(user.ShowCmd) userCmd.AddCommand(user.ShowCmd)
userCmd.AddCommand(user.ResetCmd)
} }
...@@ -20,7 +20,7 @@ const ( ...@@ -20,7 +20,7 @@ const (
DEF_PERMISSION = "0700" // default user dir permission DEF_PERMISSION = "0700" // default user dir permission
BLK_PERMISSION = "0000" // user dir permission if blocked BLK_PERMISSION = "0000" // user dir permission if blocked
HTML_BASE_PERM = "0755" // set public_html to this on creation HTML_BASE_PERM = "0755" // set public_html to this on creation
MAX_VARIANCE = 50 // tries made to create login automatically MAX_VARIANCE = 200 // tries made to create login automatically
MIN_UID = 1000 MIN_UID = 1000
MAX_UID = 6000 MAX_UID = 6000
) )
...@@ -239,42 +239,44 @@ func genLogin(name string, grr string, ltype LoginType, variance int) string { ...@@ -239,42 +239,44 @@ func genLogin(name string, grr string, ltype LoginType, variance int) string {
return "_" return "_"
} }
part_prefix_len := make([]int, len(parts)) partPrefixLen := make([]int, len(parts))
if ltype == Initials { if ltype == Initials {
// a primeira letra de cada parte deve aparecer // the first letter of each part must appear
for i := 0; i < len(parts); i++ { for i := 0; i < len(parts); i++ {
part_prefix_len[i] = 1 partPrefixLen[i] = 1
} }
} else if ltype == FirstName { } else if ltype == FirstName {
// a primeira parte inteira deve aparecer // the first name(part) must appear
part_prefix_len[0] = len(parts[0]) partPrefixLen[0] = len(parts[0])
} else { } else {
// a primeira letra de cada parte deve aparecer // the first letter of each part must appear
for i := 0; i < len(parts); i++ { for i := 0; i < len(parts); i++ {
part_prefix_len[i] = 1 partPrefixLen[i] = 1
} }
// assim com a parte final do nome // just as the last part
part_prefix_len[len(parts)-1] = len(parts[len(parts)-1]) partPrefixLen[len(parts)-1] = len(parts[len(parts)-1])
} }
part_prefix_ix := 0
partPrefixIx := 0
for i := 0; i < variance; i++ { for i := 0; i < variance; i++ {
ok := false ok := false
for k := 0; k < len(parts) && !ok; k++ { for k := 0; k < len(parts) && !ok; k++ {
if part_prefix_len[part_prefix_ix] < len(parts[part_prefix_ix]) { if partPrefixLen[partPrefixIx] < len(parts[partPrefixIx]) {
part_prefix_len[part_prefix_ix]++ partPrefixLen[partPrefixIx]++
ok = true ok = true
} }
part_prefix_ix = (part_prefix_ix + 1) % len(parts) partPrefixIx = (partPrefixIx + 1) % len(parts)
} }
if !ok { if !ok {
// acabou o que fazer, vamos abandonar porque não vai mudar mais nada // it's joever, from now on nothing happens, quit :D
break break
} }
} }
// contruct the login with the given legths
login := "" login := ""
for i := 0; i < len(parts); i++ { for i := 0; i < len(parts); i++ {
login += parts[i][:part_prefix_len[i]] login += parts[i][:partPrefixLen[i]]
} }
if login == "" { if login == "" {
return "_" return "_"
...@@ -396,21 +398,6 @@ func addKerberosPrincipal(login string) error { ...@@ -396,21 +398,6 @@ func addKerberosPrincipal(login string) error {
return nil return nil
} }
// sets the password for the user (via kerberos)
func modKerberosPassword(login, password string) error {
cmd := exec.Command("kadmin.local", "-q",
fmt.Sprintf("cpw -pw %s %s", password, login))
cmd.Stdout = nil
cmd.Stderr = nil
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to change password for %s: %v\nOutput: %s", login, err, output)
}
return nil
}
func createUserDirs(u model.User) error { func createUserDirs(u model.User) error {
success := false success := false
......
...@@ -2,31 +2,32 @@ package user ...@@ -2,31 +2,32 @@ package user
import ( import (
"bufio" "bufio"
"crypto/rand"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"github.com/go-ldap/ldap/v3"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitlab.c3sl.ufpr.br/tss24/useradm/model" "gitlab.c3sl.ufpr.br/tss24/useradm/model"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type cfg struct { type cfg struct {
Name string `yaml:"name"` Name string `yaml:"Name"`
GRR string `yaml:"grr"` GRR string `yaml:"GRR"`
Group string `yaml:"group"` Group string `yaml:"Group"`
Status string `yaml:"status"` Status string `yaml:"Status"`
Shell string `yaml:"shell"` Shell string `yaml:"Shell"`
Course string `yaml:"course"` Course string `yaml:"Course"`
Resp string `yaml:"resp"` Resp string `yaml:"Resp"`
Expiry string `yaml:"expiry"` Expiry string `yaml:"Expiry"`
} }
var ModCmd = &cobra.Command{ var ModCmd = &cobra.Command{
Use: "mod [username]", Use: "mod [username]",
Short: "Modify user information", Short: "Modify user information",
Long: "Opens a file for editing the users config. Uses $EDITOR variable",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: modifyUserFunc, RunE: modifyUserFunc,
} }
...@@ -44,41 +45,144 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error { ...@@ -44,41 +45,144 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error {
err = fmt.Errorf("More than one user found") err = fmt.Errorf("More than one user found")
return err return err
} }
u := res[0] curr := res[0]
state := cfg{ state := cfg{
Name: u.Name, Name: curr.Name,
GRR: u.GRR, GRR: curr.GRR,
Group: u.GID, Group: curr.GID,
Status: u.Status, Status: curr.Status,
Shell: u.Shell, Shell: curr.Shell,
Course: u.Course, Course: curr.Course,
Resp: u.Resp, Resp: curr.Resp,
Expiry: u.Expiry, Expiry: curr.Expiry,
} }
// Criar arquivo temporário changes, err := promptUserYaml(state)
err = validateGRR(changes.GRR)
if err != nil {
return err
}
err = validateGID(changes.Group)
if err != nil {
return err
}
err = validateStatus(changes.Status)
if err != nil {
return err
}
err = validateExpiry(changes.Expiry)
if err != nil {
return err
}
req, err := genRequest(curr, changes)
if err != nil {
return err
}
l, err := connLDAP()
if err != nil {
return err
}
defer l.Close()
if err := l.Modify(req); err != nil {
return fmt.Errorf("Failed to update user attributes: %v", err)
}
fmt.Printf("Changes applied!\n")
return nil
}
func genRequest(curr model.User, changes cfg) (*ldap.ModifyRequest, error) {
req := ldap.NewModifyRequest(curr.DN, nil)
// changed name
if curr.Name != changes.Name {
req.Replace("cn", []string{changes.Name})
}
// changed status
if curr.Status != changes.Status {
if changes.Status == "Active" && changes.Shell == "/bin/false" {
req.Replace("loginShell", []string{"/bin/bash"})
} else if changes.Status == "Active" {
req.Replace("loginShell", []string{changes.Shell})
} else {
req.Replace("loginShell", []string{"/bin/false"})
}
} else {
// only changed shell
if curr.Shell != changes.Shell {
req.Replace("loginShell", []string{changes.Shell})
}
}
// changed base group
if curr.GID != changes.Group {
groups, err := getGroups()
if err != nil {
return req, err
}
var gidNumber string
for id, gid := range groups {
if gid == changes.Group {
gidNumber = id
break
}
}
req.Replace("gidNumber", []string{gidNumber})
}
changed := applyChangesToUser(curr, changes)
changed = genGecos(changed)
// changed gecos
if curr.Gecos != changed.Gecos {
req.Replace("gecos", []string{changed.Gecos})
}
return req, nil
}
func applyChangesToUser(c model.User, n cfg) model.User {
c.GRR = n.GRR
c.Name = n.Name
c.Resp = n.Resp
c.Shell = n.Shell
c.Status = n.Status
c.Course = n.Course
c.Expiry = n.Expiry
return c
}
func promptUserYaml(state cfg) (cfg, error) {
var newState cfg
tmpFile, err := os.CreateTemp("", "config-*.yaml") tmpFile, err := os.CreateTemp("", "config-*.yaml")
if err != nil { if err != nil {
err = fmt.Errorf("Error trying to create temp file: %v", err) err = fmt.Errorf("Error trying to create temp file: %v", err)
return err return newState, err
} }
defer os.Remove(tmpFile.Name()) defer os.Remove(tmpFile.Name())
// Serializar apenas os campos editáveis
data, err := yaml.Marshal(state) data, err := yaml.Marshal(state)
if err != nil { if err != nil {
err = fmt.Errorf("Error serializing the yaml: %v", err) return newState, fmt.Errorf("Error serializing the yaml: %v", err)
return err
} }
// Escrever no arquivo temporário
if err := os.WriteFile(tmpFile.Name(), data, 0644); err != nil { if err := os.WriteFile(tmpFile.Name(), data, 0644); err != nil {
err = fmt.Errorf("Error writing to temp file: %v", err) return newState, fmt.Errorf("Error writing to temp file: %v", err)
return err
} }
// Abrir no editor // abrir o editor
editor := os.Getenv("EDITOR") editor := os.Getenv("EDITOR")
if editor == "" { if editor == "" {
editor = "nano" editor = "nano"
...@@ -90,42 +194,19 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error { ...@@ -90,42 +194,19 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error {
comd.Stderr = os.Stderr comd.Stderr = os.Stderr
if err := comd.Run(); err != nil { if err := comd.Run(); err != nil {
err = fmt.Errorf("Error opening the editor: %v", err) return newState, fmt.Errorf("Error opening the editor: %v", err)
return err
} }
// Ler o conteúdo editado
editedData, err := os.ReadFile(tmpFile.Name()) editedData, err := os.ReadFile(tmpFile.Name())
if err != nil { if err != nil {
err = fmt.Errorf("Error reading the modified file: %v", err) return newState, fmt.Errorf("Error reading the modified file: %v", err)
return err
} }
// Desserializar apenas os campos editáveis
var newState cfg
if err := yaml.Unmarshal(editedData, &newState); err != nil { if err := yaml.Unmarshal(editedData, &newState); err != nil {
err = fmt.Errorf("Error de-serializing the yaml: %v", err) return newState, fmt.Errorf("Error de-serializing the yaml: %v", err)
return err
}
// Exibir a struct atualizada
fmt.Println("New config:")
fmt.Printf("%+v\n", newState)
return nil
} }
// do NOT include ':' in the charset, it WILL break the command return newState, nil
func genPassword() string {
const charset = "@*()=+[];,.?123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
b := make([]byte, 20)
if _, err := rand.Read(b); err != nil {
panic(err)
}
for i := range b {
b[i] = charset[int(b[i])%len(charset)]
}
return string(b)
} }
// prints a confirmation prompt, given the operation being performed // prints a confirmation prompt, given the operation being performed
......
package user
import (
"crypto/rand"
"fmt"
"os/exec"
"github.com/spf13/cobra"
)
var ResetCmd = &cobra.Command{
Use: "reset [username]",
Short: "Reset the password of a user",
Args: cobra.ExactArgs(1),
RunE: resetPass,
}
func init() {
ResetCmd.Flags().StringP("passwd", "p", "", "User's new password")
}
func resetPass(cmd *cobra.Command, args []string) error {
pass, err := cmd.Flags().GetString("passwd")
if err != nil {
return err
}
users, err := getUsers()
if err != nil {
return err
}
login := args[0]
res := searchUser(users, false, login, "", "", "", "", "")
if len(res) != 1 {
return fmt.Errorf("More than one user found")
}
if pass == "" {
pass = genPassword()
}
err = modKerberosPassword(login, pass)
if err != nil {
return err
}
fmt.Printf("Password for user %v has been reset!\n", login)
fmt.Printf("New Password: %v\n", pass)
return nil
}
// do NOT include ':' in the charset, it WILL break the command
func genPassword() string {
const charset = "@*()=+[];,.?123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
b := make([]byte, 20)
if _, err := rand.Read(b); err != nil {
panic(err)
}
for i := range b {
b[i] = charset[int(b[i])%len(charset)]
}
return string(b)
}
// command that changes the password >:D
func modKerberosPassword(login, password string) error {
cmd := exec.Command("kadmin.local", "-q",
fmt.Sprintf("cpw -pw %s %s", password, login))
cmd.Stdout = nil
cmd.Stderr = nil
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to change password for %s: %v\nOutput: %s", login, err, output)
}
return nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment