]> git.wincent.com - passage.git/blob - passage.go
7ccb548bfb1bedf9dac07f2d54641a12bac99a96
[passage.git] / passage.go
1 package main
2
3 import (
4         "encoding/json"
5         "github.com/keybase/go-keychain"
6         "log"
7         "net"
8         "os"
9         "os/signal"
10         "os/user"
11         "syscall"
12 )
13
14 type Request struct {
15         Account string
16         Service string
17 }
18
19 var cache map[string][]byte
20
21 func main() {
22         resetCache()
23         path := getSockPath()
24         syscall.Umask(0077)
25         listener, err := net.Listen("unix", path)
26         if err != nil {
27                 log.Fatal(err)
28         }
29         defer listener.Close()
30
31         go func() {
32                 for {
33                         conn, err := listener.Accept()
34                         if err != nil {
35                                 log.Print(err)
36                                 return
37                         }
38                         go handleConnection(conn)
39                 }
40         }()
41         log.Print("Open and ready for business on UNIX socket at ", path)
42
43         reload := make(chan os.Signal, 1)
44         signal.Notify(reload, syscall.SIGUSR1)
45         go func() {
46                 for {
47                         sig := <-reload
48                         log.Print("Got signal ", sig, ": resetting")
49                         resetCache()
50                 }
51         }()
52
53         // Need to catch signals in order for `defer`-ed clean-up items to run.
54         term := make(chan os.Signal, 1)
55         signal.Notify(term, os.Interrupt, os.Kill, syscall.SIGTERM)
56         sig := <-term
57         log.Print("Got signal ", sig)
58 }
59
60 func resetCache() {
61         cache = make(map[string][]byte)
62 }
63
64 func getSockPath() string {
65         user, err := user.Current()
66         if err != nil {
67                 log.Fatal(err)
68         }
69         return user.HomeDir + "/.passage.sock"
70 }
71
72 func handleConnection(conn net.Conn) {
73         defer conn.Close()
74         decoder := json.NewDecoder(conn)
75         var request Request
76         err := decoder.Decode(&request)
77         if err != nil {
78                 log.Print(err)
79                 return
80         }
81
82         cache_key, err := json.Marshal(request)
83         if err != nil {
84                 log.Fatal(err)
85         }
86         if cached, exists := cache[string(cache_key)]; exists {
87                 conn.Write(cached)
88                 log.Print("Wrote result from cache")
89                 return
90         }
91
92         query := keychain.NewItem()
93         query.SetSecClass(keychain.SecClassGenericPassword)
94         query.SetService(request.Service)
95         query.SetAccount(request.Account)
96         query.SetMatchLimit(keychain.MatchLimitOne)
97         query.SetReturnData(true)
98         results, err := keychain.QueryItem(query)
99         if err != nil {
100                 log.Print(err)
101         } else if len(results) != 1 {
102                 log.Print("Item not found")
103         } else {
104                 password := results[0].Data
105                 conn.Write(password)
106                 cache[string(cache_key)] = password
107                 log.Print("Wrote result from keychain")
108         }
109 }