]> git.wincent.com - passage.git/blob - passage.go
Prep for 1.0 release
[passage.git] / passage.go
1 // Copyright 2016-present Greg Hurrell. All rights reserved.
2 // Licensed under the terms of the BSD 2-clause license.
3
4 package main
5
6 import (
7         "encoding/json"
8         "github.com/keybase/go-keychain"
9         "log"
10         "net"
11         "os"
12         "os/signal"
13         "os/user"
14         "syscall"
15 )
16
17 type Request struct {
18         Account string
19         Service string
20 }
21
22 var cache map[string][]byte
23 var version = "unknown"
24
25 func main() {
26         resetCache()
27         path := getSockPath()
28
29         // Check to see if there is a pre-existing or stale socket present.
30         if _, err := os.Stat(path); !os.IsNotExist(err) {
31                 // Socket already exists.
32                 if _, err = net.Dial("unix", path); err == nil {
33                         // Socket is live!
34                         log.Fatal("Live socket already exists at: " + path)
35                 }
36
37                 // Likely a stale socket left over after a crash.
38                 log.Print("Dead socket found at: " + path + " (removing)")
39                 if err = os.Remove(path); err != nil {
40                         log.Fatal(err)
41                 }
42         }
43
44         // Start listening.
45         syscall.Umask(0077)
46         listener, err := net.Listen("unix", path)
47         if err != nil {
48                 log.Fatal(err)
49         }
50         defer listener.Close()
51
52         go func() {
53                 for {
54                         conn, err := listener.Accept()
55                         if err != nil {
56                                 log.Print(err)
57                                 return
58                         }
59                         go handleConnection(conn)
60                 }
61         }()
62         log.Print("passage (version ", version, ") ready for business on UNIX socket at ", path)
63
64         reload := make(chan os.Signal, 1)
65         signal.Notify(reload, syscall.SIGUSR1)
66         go func() {
67                 for {
68                         sig := <-reload
69                         log.Print("Got signal ", sig, ": resetting")
70                         resetCache()
71                 }
72         }()
73
74         // Need to catch signals in order for `defer`-ed clean-up items to run.
75         term := make(chan os.Signal, 1)
76         signal.Notify(term, os.Interrupt, os.Kill, syscall.SIGTERM)
77         sig := <-term
78         log.Print("Got signal ", sig)
79 }
80
81 func resetCache() {
82         cache = make(map[string][]byte)
83 }
84
85 func getSockPath() string {
86         user, err := user.Current()
87         if err != nil {
88                 log.Fatal(err)
89         }
90         return user.HomeDir + "/.passage.sock"
91 }
92
93 func handleConnection(conn net.Conn) {
94         defer conn.Close()
95         decoder := json.NewDecoder(conn)
96         var request Request
97         err := decoder.Decode(&request)
98         if err != nil {
99                 log.Print(err)
100                 return
101         }
102
103         cache_key, err := json.Marshal(request)
104         if err != nil {
105                 log.Fatal(err)
106         }
107         if cached, exists := cache[string(cache_key)]; exists {
108                 conn.Write(cached)
109                 log.Print("Wrote result from cache")
110                 return
111         }
112
113         query := keychain.NewItem()
114         query.SetSecClass(keychain.SecClassGenericPassword)
115         query.SetService(request.Service)
116         query.SetAccount(request.Account)
117         query.SetMatchLimit(keychain.MatchLimitOne)
118         query.SetReturnData(true)
119         results, err := keychain.QueryItem(query)
120         if err != nil {
121                 log.Print(err)
122         } else if len(results) != 1 {
123                 log.Print("Item not found")
124         } else {
125                 password := results[0].Data
126                 conn.Write(password)
127                 cache[string(cache_key)] = password
128                 log.Print("Wrote result from keychain")
129         }
130 }