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

Last change on this file since 311 was 308, checked in by admin, 5 years ago

Allow to read password when stdin is not a tty.

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