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