source: code/trunk/cmd/sojuctl/main.go@ 736

Last change on this file since 736 was 695, checked in by contact, 4 years ago

sojuctl: use background context

File size: 3.1 KB
RevLine 
[98]1package main
2
3import (
[308]4 "bufio"
[652]5 "context"
[98]6 "flag"
7 "fmt"
[503]8 "io"
[98]9 "log"
10 "os"
11
12 "git.sr.ht/~emersion/soju"
13 "git.sr.ht/~emersion/soju/config"
14 "golang.org/x/crypto/bcrypt"
15 "golang.org/x/crypto/ssh/terminal"
16)
17
18const usage = `usage: sojuctl [-config path] <action> [options...]
19
[330]20 create-user <username> [-admin] Create a new user
21 change-password <username> Change password for a user
22 help Show this help message
[98]23`
24
25func init() {
26 flag.Usage = func() {
27 fmt.Fprintf(flag.CommandLine.Output(), usage)
28 }
29}
30
31func main() {
32 var configPath string
33 flag.StringVar(&configPath, "config", "", "path to configuration file")
34 flag.Parse()
35
36 var cfg *config.Server
37 if configPath != "" {
38 var err error
39 cfg, err = config.Load(configPath)
40 if err != nil {
41 log.Fatalf("failed to load config file: %v", err)
42 }
43 } else {
44 cfg = config.Defaults()
45 }
46
[620]47 db, err := soju.OpenDB(cfg.SQLDriver, cfg.SQLSource)
[98]48 if err != nil {
49 log.Fatalf("failed to open database: %v", err)
50 }
51
[695]52 ctx := context.Background()
53
[98]54 switch cmd := flag.Arg(0); cmd {
55 case "create-user":
56 username := flag.Arg(1)
57 if username == "" {
58 flag.Usage()
59 os.Exit(1)
60 }
61
[330]62 fs := flag.NewFlagSet("", flag.ExitOnError)
63 admin := fs.Bool("admin", false, "make the new user admin")
64 fs.Parse(flag.Args()[2:])
65
[308]66 password, err := readPassword()
[98]67 if err != nil {
68 log.Fatalf("failed to read password: %v", err)
69 }
70
71 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
72 if err != nil {
73 log.Fatalf("failed to hash password: %v", err)
74 }
75
76 user := soju.User{
77 Username: username,
78 Password: string(hashed),
[330]79 Admin: *admin,
[98]80 }
[695]81 if err := db.StoreUser(ctx, &user); err != nil {
[98]82 log.Fatalf("failed to create user: %v", err)
83 }
[251]84 case "change-password":
85 username := flag.Arg(1)
86 if username == "" {
87 flag.Usage()
88 os.Exit(1)
89 }
90
[695]91 user, err := db.GetUser(ctx, username)
[432]92 if err != nil {
93 log.Fatalf("failed to get user: %v", err)
94 }
95
[308]96 password, err := readPassword()
[251]97 if err != nil {
[308]98 log.Fatalf("failed to read password: %v", err)
[251]99 }
100
101 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
102 if err != nil {
103 log.Fatalf("failed to hash password: %v", err)
104 }
105
[380]106 user.Password = string(hashed)
[695]107 if err := db.StoreUser(ctx, user); err != nil {
[251]108 log.Fatalf("failed to update password: %v", err)
109 }
[98]110 default:
111 flag.Usage()
112 if cmd != "help" {
113 os.Exit(1)
114 }
115 }
116}
[308]117
118func readPassword() ([]byte, error) {
119 var password []byte
120 var err error
121 fd := int(os.Stdin.Fd())
122
123 if terminal.IsTerminal(fd) {
124 fmt.Printf("Password: ")
125 password, err = terminal.ReadPassword(int(os.Stdin.Fd()))
126 if err != nil {
127 return nil, err
128 }
129 fmt.Printf("\n")
130 } else {
131 fmt.Fprintf(os.Stderr, "Warning: Reading password from stdin.\n")
[503]132 // TODO: the buffering messes up repeated calls to readPassword
[308]133 scanner := bufio.NewScanner(os.Stdin)
[326]134 if !scanner.Scan() {
135 if err := scanner.Err(); err != nil {
[503]136 return nil, err
[326]137 }
[503]138 return nil, io.ErrUnexpectedEOF
[326]139 }
[308]140 password = scanner.Bytes()
141
142 if len(password) == 0 {
143 return nil, fmt.Errorf("zero length password")
144 }
145 }
146
147 return password, nil
148}
Note: See TracBrowser for help on using the repository browser.