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

Last change on this file since 693 was 652, checked in by contact, 4 years ago

Add context args to Database interface

This is a mecanical change, which just lifts up the context.TODO()
calls from inside the DB implementations to the callers.

Future work involves properly wiring up the contexts when it makes
sense.

File size: 3.1 KB
Line 
1package main
2
3import (
4 "bufio"
5 "context"
6 "flag"
7 "fmt"
8 "io"
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
20 create-user <username> [-admin] Create a new user
21 change-password <username> Change password for a user
22 help Show this help message
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
47 db, err := soju.OpenDB(cfg.SQLDriver, cfg.SQLSource)
48 if err != nil {
49 log.Fatalf("failed to open database: %v", err)
50 }
51
52 switch cmd := flag.Arg(0); cmd {
53 case "create-user":
54 username := flag.Arg(1)
55 if username == "" {
56 flag.Usage()
57 os.Exit(1)
58 }
59
60 fs := flag.NewFlagSet("", flag.ExitOnError)
61 admin := fs.Bool("admin", false, "make the new user admin")
62 fs.Parse(flag.Args()[2:])
63
64 password, err := readPassword()
65 if err != nil {
66 log.Fatalf("failed to read password: %v", err)
67 }
68
69 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
70 if err != nil {
71 log.Fatalf("failed to hash password: %v", err)
72 }
73
74 user := soju.User{
75 Username: username,
76 Password: string(hashed),
77 Admin: *admin,
78 }
79 if err := db.StoreUser(context.TODO(), &user); err != nil {
80 log.Fatalf("failed to create user: %v", err)
81 }
82 case "change-password":
83 username := flag.Arg(1)
84 if username == "" {
85 flag.Usage()
86 os.Exit(1)
87 }
88
89 user, err := db.GetUser(context.TODO(), username)
90 if err != nil {
91 log.Fatalf("failed to get user: %v", err)
92 }
93
94 password, err := readPassword()
95 if err != nil {
96 log.Fatalf("failed to read password: %v", err)
97 }
98
99 hashed, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
100 if err != nil {
101 log.Fatalf("failed to hash password: %v", err)
102 }
103
104 user.Password = string(hashed)
105 if err := db.StoreUser(context.TODO(), user); err != nil {
106 log.Fatalf("failed to update password: %v", err)
107 }
108 default:
109 flag.Usage()
110 if cmd != "help" {
111 os.Exit(1)
112 }
113 }
114}
115
116func readPassword() ([]byte, error) {
117 var password []byte
118 var err error
119 fd := int(os.Stdin.Fd())
120
121 if terminal.IsTerminal(fd) {
122 fmt.Printf("Password: ")
123 password, err = terminal.ReadPassword(int(os.Stdin.Fd()))
124 if err != nil {
125 return nil, err
126 }
127 fmt.Printf("\n")
128 } else {
129 fmt.Fprintf(os.Stderr, "Warning: Reading password from stdin.\n")
130 // TODO: the buffering messes up repeated calls to readPassword
131 scanner := bufio.NewScanner(os.Stdin)
132 if !scanner.Scan() {
133 if err := scanner.Err(); err != nil {
134 return nil, err
135 }
136 return nil, io.ErrUnexpectedEOF
137 }
138 password = scanner.Bytes()
139
140 if len(password) == 0 {
141 return nil, fmt.Errorf("zero length password")
142 }
143 }
144
145 return password, nil
146}
Note: See TracBrowser for help on using the repository browser.