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

Last change on this file since 581 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
RevLine 
[98]1package main
2
3import (
[308]4 "bufio"
[98]5 "flag"
6 "fmt"
[503]7 "io"
[98]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
[330]19 create-user <username> [-admin] Create a new user
20 change-password <username> Change password for a user
21 help Show this help message
[98]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
[531]46 db, err := soju.OpenSqliteDB(cfg.SQLDriver, cfg.SQLSource)
[98]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
[330]59 fs := flag.NewFlagSet("", flag.ExitOnError)
60 admin := fs.Bool("admin", false, "make the new user admin")
61 fs.Parse(flag.Args()[2:])
62
[308]63 password, err := readPassword()
[98]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),
[330]76 Admin: *admin,
[98]77 }
[324]78 if err := db.StoreUser(&user); err != nil {
[98]79 log.Fatalf("failed to create user: %v", err)
80 }
[251]81 case "change-password":
82 username := flag.Arg(1)
83 if username == "" {
84 flag.Usage()
85 os.Exit(1)
86 }
87
[432]88 user, err := db.GetUser(username)
89 if err != nil {
90 log.Fatalf("failed to get user: %v", err)
91 }
92
[308]93 password, err := readPassword()
[251]94 if err != nil {
[308]95 log.Fatalf("failed to read password: %v", err)
[251]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
[380]103 user.Password = string(hashed)
104 if err := db.StoreUser(user); err != nil {
[251]105 log.Fatalf("failed to update password: %v", err)
106 }
[98]107 default:
108 flag.Usage()
109 if cmd != "help" {
110 os.Exit(1)
111 }
112 }
113}
[308]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")
[503]129 // TODO: the buffering messes up repeated calls to readPassword
[308]130 scanner := bufio.NewScanner(os.Stdin)
[326]131 if !scanner.Scan() {
132 if err := scanner.Err(); err != nil {
[503]133 return nil, err
[326]134 }
[503]135 return nil, io.ErrUnexpectedEOF
[326]136 }
[308]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.