FIXME: hotpatch for compatibility with latest hg
[git] / credential-cache--daemon.c
1 #include "cache.h"
2 #include "credential.h"
3 #include "unix-socket.h"
4
5 struct credential_cache_entry {
6         struct credential item;
7         unsigned long expiration;
8 };
9 static struct credential_cache_entry *entries;
10 static int entries_nr;
11 static int entries_alloc;
12
13 static void cache_credential(const struct credential *c, int timeout)
14 {
15         struct credential_cache_entry *e;
16
17         ALLOC_GROW(entries, entries_nr + 1, entries_alloc);
18         e = &entries[entries_nr++];
19
20         memcpy(&e->item, c, sizeof(*c));
21         e->expiration = time(NULL) + timeout;
22 }
23
24 static struct credential_cache_entry *lookup_credential(const struct credential *c)
25 {
26         int i;
27         for (i = 0; i < entries_nr; i++) {
28                 struct credential *e = &entries[i].item;
29
30                 /* We must either both have the same unique token,
31                  * or we must not be using unique tokens at all. */
32                 if (e->unique) {
33                         if (!c->unique || strcmp(e->unique, c->unique))
34                                 continue;
35                 }
36                 else if (c->unique)
37                         continue;
38
39                 /* If we have a username, it must match. Otherwise,
40                  * we will fill in the username. */
41                 if (c->username && strcmp(e->username, c->username))
42                         continue;
43
44                 return &entries[i];
45         }
46         return NULL;
47 }
48
49 static void remove_credential(const struct credential *c)
50 {
51         struct credential_cache_entry *e;
52
53         e = lookup_credential(c);
54         if (e)
55                 e->expiration = 0;
56 }
57
58 static int check_expirations(void)
59 {
60         int i = 0;
61         unsigned long now = time(NULL);
62         unsigned long next = (unsigned long)-1;
63
64         while (i < entries_nr) {
65                 if (entries[i].expiration <= now) {
66                         entries_nr--;
67                         if (!entries_nr)
68                                 return 0;
69                         free(entries[i].item.description);
70                         free(entries[i].item.unique);
71                         free(entries[i].item.username);
72                         free(entries[i].item.password);
73                         memcpy(&entries[i], &entries[entries_nr], sizeof(*entries));
74                 }
75                 else {
76                         if (entries[i].expiration < next)
77                                 next = entries[i].expiration;
78                         i++;
79                 }
80         }
81
82         return next - now;
83 }
84
85 static int read_credential_request(FILE *fh, struct credential *c,
86                                    char **action, int *timeout) {
87         struct strbuf item = STRBUF_INIT;
88
89         while (strbuf_getline(&item, fh, '\0') != EOF) {
90                 char *key = item.buf;
91                 char *value = strchr(key, '=');
92
93                 if (!value) {
94                         warning("cache client sent bogus input: %s", key);
95                         strbuf_release(&item);
96                         return -1;
97                 }
98                 *value++ = '\0';
99
100                 if (!strcmp(key, "action"))
101                         *action = xstrdup(value);
102                 else if (!strcmp(key, "unique"))
103                         c->unique = xstrdup(value);
104                 else if (!strcmp(key, "username"))
105                         c->username = xstrdup(value);
106                 else if (!strcmp(key, "password"))
107                         c->password = xstrdup(value);
108                 else if (!strcmp(key, "timeout"))
109                         *timeout = atoi(value);
110                 else {
111                         warning("cache client sent bogus key: %s", key);
112                         strbuf_release(&item);
113                         return -1;
114                 }
115         }
116         strbuf_release(&item);
117         return 0;
118 }
119
120 static void serve_one_client(FILE *in, FILE *out)
121 {
122         struct credential c = { NULL };
123         int timeout = -1;
124         char *action = NULL;
125
126         if (read_credential_request(in, &c, &action, &timeout) < 0)
127                 return;
128
129         if (!action) {
130                 warning("cache client didn't specify an action");
131                 return;
132         }
133
134         if (!strcmp(action, "exit"))
135                 exit(0);
136
137         if (!strcmp(action, "get")) {
138                 struct credential_cache_entry *e = lookup_credential(&c);
139                 if (e) {
140                         fprintf(out, "username=%s\n", e->item.username);
141                         fprintf(out, "password=%s\n", e->item.password);
142                 }
143                 return;
144         }
145
146         if (!strcmp(action, "erase")) {
147                 remove_credential(&c);
148                 return;
149         }
150
151         if (!strcmp(action, "store")) {
152                 if (timeout < 0) {
153                         warning("cache client didn't specify a timeout");
154                         return;
155                 }
156
157                 remove_credential(&c);
158                 cache_credential(&c, timeout);
159                 return;
160         }
161
162         warning("cache client sent unknown action: %s", action);
163         return;
164 }
165
166 static int serve_cache_loop(int fd)
167 {
168         struct pollfd pfd;
169         unsigned long wakeup;
170
171         wakeup = check_expirations();
172         if (!wakeup)
173                 return 0;
174
175         pfd.fd = fd;
176         pfd.events = POLLIN;
177         if (poll(&pfd, 1, 1000 * wakeup) < 0) {
178                 if (errno != EINTR)
179                         die_errno("poll failed");
180                 return 1;
181         }
182
183         if (pfd.revents & POLLIN) {
184                 int client, client2;
185                 FILE *in, *out;
186
187                 client = accept(fd, NULL, NULL);
188                 if (client < 0) {
189                         warning("accept failed: %s", strerror(errno));
190                         return 1;
191                 }
192                 client2 = dup(client);
193                 if (client2 < 0) {
194                         warning("dup failed: %s", strerror(errno));
195                         close(client);
196                         return 1;
197                 }
198
199                 in = xfdopen(client, "r");
200                 out = xfdopen(client2, "w");
201                 serve_one_client(in, out);
202                 fclose(in);
203                 fclose(out);
204         }
205         return 1;
206 }
207
208 static void serve_cache(const char *socket_path)
209 {
210         int fd;
211
212         fd = unix_stream_listen(socket_path);
213         if (fd < 0)
214                 die_errno("unable to bind to '%s'", socket_path);
215
216         printf("ok\n");
217         fclose(stdout);
218
219         while (serve_cache_loop(fd))
220                 ; /* nothing */
221
222         close(fd);
223         unlink(socket_path);
224 }
225
226 static const char permissions_advice[] =
227 "The permissions on your socket directory are too loose; other\n"
228 "users may be able to read your cached credentials. Consider running:\n"
229 "\n"
230 "       chmod 0700 %s";
231 static void check_socket_directory(const char *path)
232 {
233         struct stat st;
234         char *path_copy = xstrdup(path);
235         char *dir = dirname(path_copy);
236
237         if (!stat(dir, &st)) {
238                 if (st.st_mode & 077)
239                         die(permissions_advice, dir);
240                 free(path_copy);
241                 return;
242         }
243
244         /*
245          * We must be sure to create the directory with the correct mode,
246          * not just chmod it after the fact; otherwise, there is a race
247          * condition in which somebody can chdir to it, sleep, then try to open
248          * our protected socket.
249          */
250         if (safe_create_leading_directories_const(dir) < 0)
251                 die_errno("unable to create directories for '%s'", dir);
252         if (mkdir(dir, 0700) < 0)
253                 die_errno("unable to mkdir '%s'", dir);
254         free(path_copy);
255 }
256
257 int main(int argc, const char **argv)
258 {
259         const char *socket_path = argv[1];
260
261         if (!socket_path)
262                 die("usage: git-credential-cache--daemon <socket_path>");
263         check_socket_directory(socket_path);
264
265         serve_cache(socket_path);
266
267         return 0;
268 }