http authentication via prompts
[git] / http.c
1 #include "http.h"
2
3 int data_received;
4 int active_requests;
5
6 #ifdef USE_CURL_MULTI
7 static int max_requests = -1;
8 static CURLM *curlm;
9 #endif
10 #ifndef NO_CURL_EASY_DUPHANDLE
11 static CURL *curl_default;
12 #endif
13 char curl_errorstr[CURL_ERROR_SIZE];
14
15 static int curl_ssl_verify = -1;
16 static const char *ssl_cert;
17 #if LIBCURL_VERSION_NUM >= 0x070902
18 static const char *ssl_key;
19 #endif
20 #if LIBCURL_VERSION_NUM >= 0x070908
21 static const char *ssl_capath;
22 #endif
23 static const char *ssl_cainfo;
24 static long curl_low_speed_limit = -1;
25 static long curl_low_speed_time = -1;
26 static int curl_ftp_no_epsv;
27 static const char *curl_http_proxy;
28 static char *user_name, *user_pass;
29
30 static struct curl_slist *pragma_header;
31
32 static struct active_request_slot *active_queue_head;
33
34 size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
35 {
36         size_t size = eltsize * nmemb;
37         struct buffer *buffer = buffer_;
38
39         if (size > buffer->buf.len - buffer->posn)
40                 size = buffer->buf.len - buffer->posn;
41         memcpy(ptr, buffer->buf.buf + buffer->posn, size);
42         buffer->posn += size;
43
44         return size;
45 }
46
47 size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
48 {
49         size_t size = eltsize * nmemb;
50         struct strbuf *buffer = buffer_;
51
52         strbuf_add(buffer, ptr, size);
53         data_received++;
54         return size;
55 }
56
57 size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
58 {
59         data_received++;
60         return eltsize * nmemb;
61 }
62
63 static void finish_active_slot(struct active_request_slot *slot);
64
65 #ifdef USE_CURL_MULTI
66 static void process_curl_messages(void)
67 {
68         int num_messages;
69         struct active_request_slot *slot;
70         CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
71
72         while (curl_message != NULL) {
73                 if (curl_message->msg == CURLMSG_DONE) {
74                         int curl_result = curl_message->data.result;
75                         slot = active_queue_head;
76                         while (slot != NULL &&
77                                slot->curl != curl_message->easy_handle)
78                                 slot = slot->next;
79                         if (slot != NULL) {
80                                 curl_multi_remove_handle(curlm, slot->curl);
81                                 slot->curl_result = curl_result;
82                                 finish_active_slot(slot);
83                         } else {
84                                 fprintf(stderr, "Received DONE message for unknown request!\n");
85                         }
86                 } else {
87                         fprintf(stderr, "Unknown CURL message received: %d\n",
88                                 (int)curl_message->msg);
89                 }
90                 curl_message = curl_multi_info_read(curlm, &num_messages);
91         }
92 }
93 #endif
94
95 static int http_options(const char *var, const char *value, void *cb)
96 {
97         if (!strcmp("http.sslverify", var)) {
98                 curl_ssl_verify = git_config_bool(var, value);
99                 return 0;
100         }
101         if (!strcmp("http.sslcert", var))
102                 return git_config_string(&ssl_cert, var, value);
103 #if LIBCURL_VERSION_NUM >= 0x070902
104         if (!strcmp("http.sslkey", var))
105                 return git_config_string(&ssl_key, var, value);
106 #endif
107 #if LIBCURL_VERSION_NUM >= 0x070908
108         if (!strcmp("http.sslcapath", var))
109                 return git_config_string(&ssl_capath, var, value);
110 #endif
111         if (!strcmp("http.sslcainfo", var))
112                 return git_config_string(&ssl_cainfo, var, value);
113 #ifdef USE_CURL_MULTI
114         if (!strcmp("http.maxrequests", var)) {
115                 max_requests = git_config_int(var, value);
116                 return 0;
117         }
118 #endif
119         if (!strcmp("http.lowspeedlimit", var)) {
120                 curl_low_speed_limit = (long)git_config_int(var, value);
121                 return 0;
122         }
123         if (!strcmp("http.lowspeedtime", var)) {
124                 curl_low_speed_time = (long)git_config_int(var, value);
125                 return 0;
126         }
127
128         if (!strcmp("http.noepsv", var)) {
129                 curl_ftp_no_epsv = git_config_bool(var, value);
130                 return 0;
131         }
132         if (!strcmp("http.proxy", var))
133                 return git_config_string(&curl_http_proxy, var, value);
134
135         /* Fall back on the default ones */
136         return git_default_config(var, value, cb);
137 }
138
139 static void init_curl_http_auth(CURL *result)
140 {
141         if (!user_name)
142                 curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
143         else {
144                 struct strbuf up = STRBUF_INIT;
145                 if (!user_pass)
146                         user_pass = xstrdup(getpass("Password: "));
147                 strbuf_addf(&up, "%s:%s", user_name, user_pass);
148                 curl_easy_setopt(result, CURLOPT_USERPWD,
149                                  strbuf_detach(&up, NULL));
150         }
151 }
152
153 static CURL *get_curl_handle(void)
154 {
155         CURL *result = curl_easy_init();
156
157         if (!curl_ssl_verify) {
158                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0);
159                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0);
160         } else {
161                 /* Verify authenticity of the peer's certificate */
162                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 1);
163                 /* The name in the cert must match whom we tried to connect */
164                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2);
165         }
166
167 #if LIBCURL_VERSION_NUM >= 0x070907
168         curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
169 #endif
170
171         init_curl_http_auth(result);
172
173         if (ssl_cert != NULL)
174                 curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
175 #if LIBCURL_VERSION_NUM >= 0x070902
176         if (ssl_key != NULL)
177                 curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
178 #endif
179 #if LIBCURL_VERSION_NUM >= 0x070908
180         if (ssl_capath != NULL)
181                 curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
182 #endif
183         if (ssl_cainfo != NULL)
184                 curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
185         curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
186
187         if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
188                 curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
189                                  curl_low_speed_limit);
190                 curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
191                                  curl_low_speed_time);
192         }
193
194         curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
195
196         if (getenv("GIT_CURL_VERBOSE"))
197                 curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
198
199         curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
200
201         if (curl_ftp_no_epsv)
202                 curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
203
204         if (curl_http_proxy)
205                 curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
206
207         return result;
208 }
209
210 static void http_auth_init(const char *url)
211 {
212         char *at, *colon, *cp, *slash;
213         int len;
214
215         cp = strstr(url, "://");
216         if (!cp)
217                 return;
218
219         /*
220          * Ok, the URL looks like "proto://something".  Which one?
221          * "proto://<user>:<pass>@<host>/...",
222          * "proto://<user>@<host>/...", or just
223          * "proto://<host>/..."?
224          */
225         cp += 3;
226         at = strchr(cp, '@');
227         colon = strchr(cp, ':');
228         slash = strchrnul(cp, '/');
229         if (!at || slash <= at)
230                 return; /* No credentials */
231         if (!colon || at <= colon) {
232                 /* Only username */
233                 len = at - cp;
234                 user_name = xmalloc(len + 1);
235                 memcpy(user_name, cp, len);
236                 user_name[len] = '\0';
237                 user_pass = NULL;
238         } else {
239                 len = colon - cp;
240                 user_name = xmalloc(len + 1);
241                 memcpy(user_name, cp, len);
242                 user_name[len] = '\0';
243                 len = at - (colon + 1);
244                 user_pass = xmalloc(len + 1);
245                 memcpy(user_pass, colon + 1, len);
246                 user_pass[len] = '\0';
247         }
248 }
249
250 static void set_from_env(const char **var, const char *envname)
251 {
252         const char *val = getenv(envname);
253         if (val)
254                 *var = val;
255 }
256
257 void http_init(struct remote *remote)
258 {
259         char *low_speed_limit;
260         char *low_speed_time;
261
262         git_config(http_options, NULL);
263
264         curl_global_init(CURL_GLOBAL_ALL);
265
266         if (remote && remote->http_proxy)
267                 curl_http_proxy = xstrdup(remote->http_proxy);
268
269         pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
270
271 #ifdef USE_CURL_MULTI
272         {
273                 char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
274                 if (http_max_requests != NULL)
275                         max_requests = atoi(http_max_requests);
276         }
277
278         curlm = curl_multi_init();
279         if (curlm == NULL) {
280                 fprintf(stderr, "Error creating curl multi handle.\n");
281                 exit(1);
282         }
283 #endif
284
285         if (getenv("GIT_SSL_NO_VERIFY"))
286                 curl_ssl_verify = 0;
287
288         set_from_env(&ssl_cert, "GIT_SSL_CERT");
289 #if LIBCURL_VERSION_NUM >= 0x070902
290         set_from_env(&ssl_key, "GIT_SSL_KEY");
291 #endif
292 #if LIBCURL_VERSION_NUM >= 0x070908
293         set_from_env(&ssl_capath, "GIT_SSL_CAPATH");
294 #endif
295         set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
296
297         low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
298         if (low_speed_limit != NULL)
299                 curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
300         low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
301         if (low_speed_time != NULL)
302                 curl_low_speed_time = strtol(low_speed_time, NULL, 10);
303
304         if (curl_ssl_verify == -1)
305                 curl_ssl_verify = 1;
306
307 #ifdef USE_CURL_MULTI
308         if (max_requests < 1)
309                 max_requests = DEFAULT_MAX_REQUESTS;
310 #endif
311
312         if (getenv("GIT_CURL_FTP_NO_EPSV"))
313                 curl_ftp_no_epsv = 1;
314
315         if (remote && remote->url && remote->url[0])
316                 http_auth_init(remote->url[0]);
317
318 #ifndef NO_CURL_EASY_DUPHANDLE
319         curl_default = get_curl_handle();
320 #endif
321 }
322
323 void http_cleanup(void)
324 {
325         struct active_request_slot *slot = active_queue_head;
326
327         while (slot != NULL) {
328                 struct active_request_slot *next = slot->next;
329                 if (slot->curl != NULL) {
330 #ifdef USE_CURL_MULTI
331                         curl_multi_remove_handle(curlm, slot->curl);
332 #endif
333                         curl_easy_cleanup(slot->curl);
334                 }
335                 free(slot);
336                 slot = next;
337         }
338         active_queue_head = NULL;
339
340 #ifndef NO_CURL_EASY_DUPHANDLE
341         curl_easy_cleanup(curl_default);
342 #endif
343
344 #ifdef USE_CURL_MULTI
345         curl_multi_cleanup(curlm);
346 #endif
347         curl_global_cleanup();
348
349         curl_slist_free_all(pragma_header);
350         pragma_header = NULL;
351
352         if (curl_http_proxy) {
353                 free((void *)curl_http_proxy);
354                 curl_http_proxy = NULL;
355         }
356 }
357
358 struct active_request_slot *get_active_slot(void)
359 {
360         struct active_request_slot *slot = active_queue_head;
361         struct active_request_slot *newslot;
362
363 #ifdef USE_CURL_MULTI
364         int num_transfers;
365
366         /* Wait for a slot to open up if the queue is full */
367         while (active_requests >= max_requests) {
368                 curl_multi_perform(curlm, &num_transfers);
369                 if (num_transfers < active_requests)
370                         process_curl_messages();
371         }
372 #endif
373
374         while (slot != NULL && slot->in_use)
375                 slot = slot->next;
376
377         if (slot == NULL) {
378                 newslot = xmalloc(sizeof(*newslot));
379                 newslot->curl = NULL;
380                 newslot->in_use = 0;
381                 newslot->next = NULL;
382
383                 slot = active_queue_head;
384                 if (slot == NULL) {
385                         active_queue_head = newslot;
386                 } else {
387                         while (slot->next != NULL)
388                                 slot = slot->next;
389                         slot->next = newslot;
390                 }
391                 slot = newslot;
392         }
393
394         if (slot->curl == NULL) {
395 #ifdef NO_CURL_EASY_DUPHANDLE
396                 slot->curl = get_curl_handle();
397 #else
398                 slot->curl = curl_easy_duphandle(curl_default);
399 #endif
400         }
401
402         active_requests++;
403         slot->in_use = 1;
404         slot->local = NULL;
405         slot->results = NULL;
406         slot->finished = NULL;
407         slot->callback_data = NULL;
408         slot->callback_func = NULL;
409         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
410         curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
411         curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
412         curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
413         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
414         curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
415         curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
416
417         return slot;
418 }
419
420 int start_active_slot(struct active_request_slot *slot)
421 {
422 #ifdef USE_CURL_MULTI
423         CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
424         int num_transfers;
425
426         if (curlm_result != CURLM_OK &&
427             curlm_result != CURLM_CALL_MULTI_PERFORM) {
428                 active_requests--;
429                 slot->in_use = 0;
430                 return 0;
431         }
432
433         /*
434          * We know there must be something to do, since we just added
435          * something.
436          */
437         curl_multi_perform(curlm, &num_transfers);
438 #endif
439         return 1;
440 }
441
442 #ifdef USE_CURL_MULTI
443 struct fill_chain {
444         void *data;
445         int (*fill)(void *);
446         struct fill_chain *next;
447 };
448
449 static struct fill_chain *fill_cfg;
450
451 void add_fill_function(void *data, int (*fill)(void *))
452 {
453         struct fill_chain *new = xmalloc(sizeof(*new));
454         struct fill_chain **linkp = &fill_cfg;
455         new->data = data;
456         new->fill = fill;
457         new->next = NULL;
458         while (*linkp)
459                 linkp = &(*linkp)->next;
460         *linkp = new;
461 }
462
463 void fill_active_slots(void)
464 {
465         struct active_request_slot *slot = active_queue_head;
466
467         while (active_requests < max_requests) {
468                 struct fill_chain *fill;
469                 for (fill = fill_cfg; fill; fill = fill->next)
470                         if (fill->fill(fill->data))
471                                 break;
472
473                 if (!fill)
474                         break;
475         }
476
477         while (slot != NULL) {
478                 if (!slot->in_use && slot->curl != NULL) {
479                         curl_easy_cleanup(slot->curl);
480                         slot->curl = NULL;
481                 }
482                 slot = slot->next;
483         }
484 }
485
486 void step_active_slots(void)
487 {
488         int num_transfers;
489         CURLMcode curlm_result;
490
491         do {
492                 curlm_result = curl_multi_perform(curlm, &num_transfers);
493         } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
494         if (num_transfers < active_requests) {
495                 process_curl_messages();
496                 fill_active_slots();
497         }
498 }
499 #endif
500
501 void run_active_slot(struct active_request_slot *slot)
502 {
503 #ifdef USE_CURL_MULTI
504         long last_pos = 0;
505         long current_pos;
506         fd_set readfds;
507         fd_set writefds;
508         fd_set excfds;
509         int max_fd;
510         struct timeval select_timeout;
511         int finished = 0;
512
513         slot->finished = &finished;
514         while (!finished) {
515                 data_received = 0;
516                 step_active_slots();
517
518                 if (!data_received && slot->local != NULL) {
519                         current_pos = ftell(slot->local);
520                         if (current_pos > last_pos)
521                                 data_received++;
522                         last_pos = current_pos;
523                 }
524
525                 if (slot->in_use && !data_received) {
526                         max_fd = 0;
527                         FD_ZERO(&readfds);
528                         FD_ZERO(&writefds);
529                         FD_ZERO(&excfds);
530                         select_timeout.tv_sec = 0;
531                         select_timeout.tv_usec = 50000;
532                         select(max_fd, &readfds, &writefds,
533                                &excfds, &select_timeout);
534                 }
535         }
536 #else
537         while (slot->in_use) {
538                 slot->curl_result = curl_easy_perform(slot->curl);
539                 finish_active_slot(slot);
540         }
541 #endif
542 }
543
544 static void closedown_active_slot(struct active_request_slot *slot)
545 {
546         active_requests--;
547         slot->in_use = 0;
548 }
549
550 void release_active_slot(struct active_request_slot *slot)
551 {
552         closedown_active_slot(slot);
553         if (slot->curl) {
554 #ifdef USE_CURL_MULTI
555                 curl_multi_remove_handle(curlm, slot->curl);
556 #endif
557                 curl_easy_cleanup(slot->curl);
558                 slot->curl = NULL;
559         }
560 #ifdef USE_CURL_MULTI
561         fill_active_slots();
562 #endif
563 }
564
565 static void finish_active_slot(struct active_request_slot *slot)
566 {
567         closedown_active_slot(slot);
568         curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
569
570         if (slot->finished != NULL)
571                 (*slot->finished) = 1;
572
573         /* Store slot results so they can be read after the slot is reused */
574         if (slot->results != NULL) {
575                 slot->results->curl_result = slot->curl_result;
576                 slot->results->http_code = slot->http_code;
577         }
578
579         /* Run callback if appropriate */
580         if (slot->callback_func != NULL)
581                 slot->callback_func(slot->callback_data);
582 }
583
584 void finish_all_active_slots(void)
585 {
586         struct active_request_slot *slot = active_queue_head;
587
588         while (slot != NULL)
589                 if (slot->in_use) {
590                         run_active_slot(slot);
591                         slot = active_queue_head;
592                 } else {
593                         slot = slot->next;
594                 }
595 }
596
597 static inline int needs_quote(int ch)
598 {
599         if (((ch >= 'A') && (ch <= 'Z'))
600                         || ((ch >= 'a') && (ch <= 'z'))
601                         || ((ch >= '0') && (ch <= '9'))
602                         || (ch == '/')
603                         || (ch == '-')
604                         || (ch == '.'))
605                 return 0;
606         return 1;
607 }
608
609 static inline int hex(int v)
610 {
611         if (v < 10)
612                 return '0' + v;
613         else
614                 return 'A' + v - 10;
615 }
616
617 static char *quote_ref_url(const char *base, const char *ref)
618 {
619         struct strbuf buf = STRBUF_INIT;
620         const char *cp;
621         int ch;
622
623         strbuf_addstr(&buf, base);
624         if (buf.len && buf.buf[buf.len - 1] != '/' && *ref != '/')
625                 strbuf_addstr(&buf, "/");
626
627         for (cp = ref; (ch = *cp) != 0; cp++)
628                 if (needs_quote(ch))
629                         strbuf_addf(&buf, "%%%02x", ch);
630                 else
631                         strbuf_addch(&buf, *cp);
632
633         return strbuf_detach(&buf, NULL);
634 }
635
636 int http_fetch_ref(const char *base, struct ref *ref)
637 {
638         char *url;
639         struct strbuf buffer = STRBUF_INIT;
640         struct active_request_slot *slot;
641         struct slot_results results;
642         int ret;
643
644         url = quote_ref_url(base, ref->name);
645         slot = get_active_slot();
646         slot->results = &results;
647         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
648         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
649         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
650         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
651         if (start_active_slot(slot)) {
652                 run_active_slot(slot);
653                 if (results.curl_result == CURLE_OK) {
654                         strbuf_rtrim(&buffer);
655                         if (buffer.len == 40)
656                                 ret = get_sha1_hex(buffer.buf, ref->old_sha1);
657                         else if (!prefixcmp(buffer.buf, "ref: ")) {
658                                 ref->symref = xstrdup(buffer.buf + 5);
659                                 ret = 0;
660                         } else
661                                 ret = 1;
662                 } else {
663                         ret = error("Couldn't get %s for %s\n%s",
664                                     url, ref->name, curl_errorstr);
665                 }
666         } else {
667                 ret = error("Unable to start request");
668         }
669
670         strbuf_release(&buffer);
671         free(url);
672         return ret;
673 }