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

Last change on this file since 535 was 531, checked in by sir, 4 years ago

db: refactor into interface

This refactors the SQLite-specific bits into db_sqlite.go. A future
patch will add PostgreSQL support.

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