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