Git fork
at reftables-rust 354 lines 9.0 kB view raw
1#define USE_THE_REPOSITORY_VARIABLE 2#include "builtin.h" 3#include "abspath.h" 4#include "gettext.h" 5#include "parse-options.h" 6#include "path.h" 7 8#ifndef NO_UNIX_SOCKETS 9 10#include "config.h" 11#include "tempfile.h" 12#include "credential.h" 13#include "unix-socket.h" 14 15struct credential_cache_entry { 16 struct credential item; 17 timestamp_t expiration; 18}; 19static struct credential_cache_entry *entries; 20static int entries_nr; 21static int entries_alloc; 22 23static void cache_credential(struct credential *c, int timeout) 24{ 25 struct credential_cache_entry *e; 26 27 ALLOC_GROW(entries, entries_nr + 1, entries_alloc); 28 e = &entries[entries_nr++]; 29 30 /* take ownership of pointers */ 31 memcpy(&e->item, c, sizeof(*c)); 32 memset(c, 0, sizeof(*c)); 33 e->expiration = time(NULL) + timeout; 34} 35 36static struct credential_cache_entry *lookup_credential(const struct credential *c) 37{ 38 int i; 39 for (i = 0; i < entries_nr; i++) { 40 struct credential *e = &entries[i].item; 41 if (credential_match(c, e, 0)) 42 return &entries[i]; 43 } 44 return NULL; 45} 46 47static void remove_credential(const struct credential *c, int match_password) 48{ 49 struct credential_cache_entry *e; 50 51 int i; 52 for (i = 0; i < entries_nr; i++) { 53 e = &entries[i]; 54 if (credential_match(c, &e->item, match_password)) 55 e->expiration = 0; 56 } 57} 58 59static timestamp_t check_expirations(void) 60{ 61 static timestamp_t wait_for_entry_until; 62 int i = 0; 63 timestamp_t now = time(NULL); 64 timestamp_t next = TIME_MAX; 65 66 /* 67 * Initially give the client 30 seconds to actually contact us 68 * and store a credential before we decide there's no point in 69 * keeping the daemon around. 70 */ 71 if (!wait_for_entry_until) 72 wait_for_entry_until = now + 30; 73 74 while (i < entries_nr) { 75 if (entries[i].expiration <= now) { 76 entries_nr--; 77 credential_clear(&entries[i].item); 78 if (i != entries_nr) 79 memcpy(&entries[i], &entries[entries_nr], sizeof(*entries)); 80 /* 81 * Stick around 30 seconds in case a new credential 82 * shows up (e.g., because we just removed a failed 83 * one, and we will soon get the correct one). 84 */ 85 wait_for_entry_until = now + 30; 86 } 87 else { 88 if (entries[i].expiration < next) 89 next = entries[i].expiration; 90 i++; 91 } 92 } 93 94 if (!entries_nr) { 95 if (wait_for_entry_until <= now) 96 return 0; 97 next = wait_for_entry_until; 98 } 99 100 return next - now; 101} 102 103static int read_request(FILE *fh, struct credential *c, 104 struct strbuf *action, int *timeout) 105{ 106 static struct strbuf item = STRBUF_INIT; 107 const char *p; 108 109 strbuf_getline_lf(&item, fh); 110 if (!skip_prefix(item.buf, "action=", &p)) 111 return error("client sent bogus action line: %s", item.buf); 112 strbuf_addstr(action, p); 113 114 strbuf_getline_lf(&item, fh); 115 if (!skip_prefix(item.buf, "timeout=", &p)) 116 return error("client sent bogus timeout line: %s", item.buf); 117 *timeout = atoi(p); 118 119 credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL); 120 121 if (credential_read(c, fh, CREDENTIAL_OP_HELPER) < 0) 122 return -1; 123 return 0; 124} 125 126static void serve_one_client(FILE *in, FILE *out) 127{ 128 struct credential c = CREDENTIAL_INIT; 129 struct strbuf action = STRBUF_INIT; 130 int timeout = -1; 131 132 if (read_request(in, &c, &action, &timeout) < 0) 133 /* ignore error */ ; 134 else if (!strcmp(action.buf, "get")) { 135 struct credential_cache_entry *e = lookup_credential(&c); 136 if (e) { 137 e->item.capa_authtype.request_initial = 1; 138 e->item.capa_authtype.request_helper = 1; 139 140 fprintf(out, "capability[]=authtype\n"); 141 if (e->item.username) 142 fprintf(out, "username=%s\n", e->item.username); 143 if (e->item.password) 144 fprintf(out, "password=%s\n", e->item.password); 145 if (credential_has_capability(&c.capa_authtype, CREDENTIAL_OP_RESPONSE) && e->item.authtype) 146 fprintf(out, "authtype=%s\n", e->item.authtype); 147 if (credential_has_capability(&c.capa_authtype, CREDENTIAL_OP_RESPONSE) && e->item.credential) 148 fprintf(out, "credential=%s\n", e->item.credential); 149 if (e->item.password_expiry_utc != TIME_MAX) 150 fprintf(out, "password_expiry_utc=%"PRItime"\n", 151 e->item.password_expiry_utc); 152 if (e->item.oauth_refresh_token) 153 fprintf(out, "oauth_refresh_token=%s\n", 154 e->item.oauth_refresh_token); 155 } 156 } 157 else if (!strcmp(action.buf, "exit")) { 158 /* 159 * It's important that we clean up our socket first, and then 160 * signal the client only once we have finished the cleanup. 161 * Calling exit() directly does this, because we clean up in 162 * our atexit() handler, and then signal the client when our 163 * process actually ends, which closes the socket and gives 164 * them EOF. 165 */ 166 exit(0); 167 } 168 else if (!strcmp(action.buf, "erase")) 169 remove_credential(&c, 1); 170 else if (!strcmp(action.buf, "store")) { 171 if (timeout < 0) 172 warning("cache client didn't specify a timeout"); 173 else if ((!c.username || !c.password) && (!c.authtype && !c.credential)) 174 warning("cache client gave us a partial credential"); 175 else if (c.ephemeral) 176 warning("not storing ephemeral credential"); 177 else { 178 remove_credential(&c, 0); 179 cache_credential(&c, timeout); 180 } 181 } 182 else 183 warning("cache client sent unknown action: %s", action.buf); 184 185 credential_clear(&c); 186 strbuf_release(&action); 187} 188 189static int serve_cache_loop(int fd) 190{ 191 struct pollfd pfd; 192 timestamp_t wakeup; 193 194 wakeup = check_expirations(); 195 if (!wakeup) 196 return 0; 197 198 pfd.fd = fd; 199 pfd.events = POLLIN; 200 if (poll(&pfd, 1, 1000 * wakeup) < 0) { 201 if (errno != EINTR) 202 die_errno("poll failed"); 203 return 1; 204 } 205 206 if (pfd.revents & POLLIN) { 207 int client, client2; 208 FILE *in, *out; 209 210 client = accept(fd, NULL, NULL); 211 if (client < 0) { 212 warning_errno("accept failed"); 213 return 1; 214 } 215 client2 = dup(client); 216 if (client2 < 0) { 217 warning_errno("dup failed"); 218 close(client); 219 return 1; 220 } 221 222 in = xfdopen(client, "r"); 223 out = xfdopen(client2, "w"); 224 serve_one_client(in, out); 225 fclose(in); 226 fclose(out); 227 } 228 return 1; 229} 230 231static void serve_cache(const char *socket_path, int debug) 232{ 233 struct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT; 234 int fd; 235 236 fd = unix_stream_listen(socket_path, &opts); 237 if (fd < 0) 238 die_errno("unable to bind to '%s'", socket_path); 239 240 printf("ok\n"); 241 fclose(stdout); 242 if (!debug) { 243 if (!freopen("/dev/null", "w", stderr)) 244 die_errno("unable to point stderr to /dev/null"); 245 } 246 247 while (serve_cache_loop(fd)) 248 ; /* nothing */ 249 250 close(fd); 251} 252 253static const char permissions_advice[] = N_( 254"The permissions on your socket directory are too loose; other\n" 255"users may be able to read your cached credentials. Consider running:\n" 256"\n" 257" chmod 0700 %s"); 258static void init_socket_directory(const char *path) 259{ 260 struct stat st; 261 char *path_copy = xstrdup(path); 262 char *dir = dirname(path_copy); 263 264 if (!stat(dir, &st)) { 265 if (st.st_mode & 077) 266 die(_(permissions_advice), dir); 267 } else { 268 /* 269 * We must be sure to create the directory with the correct mode, 270 * not just chmod it after the fact; otherwise, there is a race 271 * condition in which somebody can chdir to it, sleep, then try to open 272 * our protected socket. 273 */ 274 if (safe_create_leading_directories_const(the_repository, dir) < 0) 275 die_errno("unable to create directories for '%s'", dir); 276 if (mkdir(dir, 0700) < 0) 277 die_errno("unable to mkdir '%s'", dir); 278 } 279 280 if (chdir(dir)) 281 /* 282 * We don't actually care what our cwd is; we chdir here just to 283 * be a friendly daemon and avoid tying up our original cwd. 284 * If this fails, it's OK to just continue without that benefit. 285 */ 286 ; 287 288 free(path_copy); 289} 290 291int cmd_credential_cache_daemon(int argc, 292 const char **argv, 293 const char *prefix, 294 struct repository *repo UNUSED) 295{ 296 struct tempfile *socket_file; 297 const char *socket_path; 298 int ignore_sighup = 0; 299 static const char *usage[] = { 300 "git credential-cache--daemon [--debug] <socket-path>", 301 NULL 302 }; 303 int debug = 0; 304 const struct option options[] = { 305 OPT_BOOL(0, "debug", &debug, 306 N_("print debugging messages to stderr")), 307 OPT_END() 308 }; 309 310 repo_config_get_bool(the_repository, "credentialcache.ignoresighup", &ignore_sighup); 311 312 argc = parse_options(argc, argv, prefix, options, usage, 0); 313 socket_path = argv[0]; 314 315 if (!have_unix_sockets()) 316 die(_("credential-cache--daemon unavailable; no unix socket support")); 317 if (!socket_path) 318 usage_with_options(usage, options); 319 320 if (!is_absolute_path(socket_path)) 321 die("socket directory must be an absolute path"); 322 323 init_socket_directory(socket_path); 324 socket_file = register_tempfile(socket_path); 325 326 if (ignore_sighup) 327 signal(SIGHUP, SIG_IGN); 328 329 serve_cache(socket_path, debug); 330 delete_tempfile(&socket_file); 331 332 return 0; 333} 334 335#else 336 337int cmd_credential_cache_daemon(int argc, 338const char **argv, 339const char *prefix, 340struct repository *repo UNUSED) 341{ 342 const char * const usage[] = { 343 "git credential-cache--daemon [--debug] <socket-path>", 344 "", 345 "credential-cache--daemon is disabled in this build of Git", 346 NULL 347 }; 348 struct option options[] = { OPT_END() }; 349 350 argc = parse_options(argc, argv, prefix, options, usage, 0); 351 die(_("credential-cache--daemon unavailable; no unix socket support")); 352} 353 354#endif /* NO_UNIX_SOCKET */