forked from mcluseau/autentigo
/
main.go
131 lines (100 loc) · 2.41 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main
import (
"context"
"encoding/json"
"flag"
"log"
"strings"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/clientv3/mirror"
)
var (
etcd *clientv3.Client
etcdURL = flag.String("etcd", "http://localhost:2379", "etcd URL")
etcdPrefix = flag.String("etcd-prefix", "/users", "Prefix of etcd keys")
passwdFile = flag.String("passwd-file", "passwd", "Dovecot passwd file")
values = map[string]string{}
)
func main() {
flag.Parse()
// connect to etcd
var err error
etcd, err = clientv3.NewFromURL(*etcdURL)
if err != nil {
log.Fatal("failed to connect to etcd: ", err)
}
if len(*etcdPrefix) != 0 && !strings.HasSuffix(*etcdPrefix, "/") {
*etcdPrefix += "/"
}
log.Print("connected to etcd with prefix ", *etcdPrefix)
// prepare mirror
rev := loadState()
if rev == 0 {
rev = initializeFromScratch()
} else {
loadFile()
}
followChanges(rev)
}
func initializeFromScratch() (rev int64) {
log.Print("initializing from scratch")
sync := mirror.NewSyncer(etcd, *etcdPrefix, 0)
gets, errors := sync.SyncBase(context.Background())
for get := range gets {
rev = get.Header.Revision
for _, kv := range get.Kvs {
setValue(kv.Key, kv.Value)
}
}
if err := <-errors; err != nil {
log.Fatal("failed to read from etcd: ", err)
}
save(rev)
return
}
func followChanges(rev int64) {
log.Print("following changes")
ctx := context.Background()
for {
sync := mirror.NewSyncer(etcd, *etcdPrefix, rev)
for update := range sync.SyncUpdates(ctx) {
if err := update.Err(); err != nil {
log.Print("syncer error: ", err)
continue
}
for _, event := range update.Events {
if event.Kv == nil {
delValue(event.Kv.Key)
} else {
setValue(event.Kv.Key, event.Kv.Value)
}
}
rev = update.Header.Revision
save(rev)
}
log.Print("sync updates ended, restarting")
}
}
func keyFrom(key []byte) string {
return string(key[len(*etcdPrefix):])
}
func setValue(fullKey []byte, value []byte) {
key := keyFrom(fullKey)
log.Print("update on ", key)
v := struct {
PasswordHash string `json:"password_hash"`
}{}
if err := json.Unmarshal(value, &v); err != nil {
log.Fatal("failed to parse value at key ", string(fullKey), ": ", err)
}
h := v.PasswordHash
if !strings.HasPrefix(h, "{") {
h = "{SHA256.HEX}" + h
}
values[key] = h
}
func delValue(fullKey []byte) {
key := keyFrom(fullKey)
log.Print("delete on ", key)
delete(values, key)
}