Code

http.c: add http.sslCertPasswordProtected option
[git.git] / http.c
1 #include "http.h"
3 int data_received;
4 int active_requests;
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];
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;
30 #if LIBCURL_VERSION_NUM >= 0x071700
31 /* Use CURLOPT_KEYPASSWD as is */
32 #elif LIBCURL_VERSION_NUM >= 0x070903
33 #define CURLOPT_KEYPASSWD CURLOPT_SSLKEYPASSWD
34 #else
35 #define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
36 #endif
38 static char *ssl_cert_password;
39 static int ssl_cert_password_required;
41 static struct curl_slist *pragma_header;
43 static struct active_request_slot *active_queue_head;
45 size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
46 {
47         size_t size = eltsize * nmemb;
48         struct buffer *buffer = buffer_;
50         if (size > buffer->buf.len - buffer->posn)
51                 size = buffer->buf.len - buffer->posn;
52         memcpy(ptr, buffer->buf.buf + buffer->posn, size);
53         buffer->posn += size;
55         return size;
56 }
58 #ifndef NO_CURL_IOCTL
59 curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
60 {
61         struct buffer *buffer = clientp;
63         switch (cmd) {
64         case CURLIOCMD_NOP:
65                 return CURLIOE_OK;
67         case CURLIOCMD_RESTARTREAD:
68                 buffer->posn = 0;
69                 return CURLIOE_OK;
71         default:
72                 return CURLIOE_UNKNOWNCMD;
73         }
74 }
75 #endif
77 size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
78 {
79         size_t size = eltsize * nmemb;
80         struct strbuf *buffer = buffer_;
82         strbuf_add(buffer, ptr, size);
83         data_received++;
84         return size;
85 }
87 size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
88 {
89         data_received++;
90         return eltsize * nmemb;
91 }
93 static void finish_active_slot(struct active_request_slot *slot);
95 #ifdef USE_CURL_MULTI
96 static void process_curl_messages(void)
97 {
98         int num_messages;
99         struct active_request_slot *slot;
100         CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
102         while (curl_message != NULL) {
103                 if (curl_message->msg == CURLMSG_DONE) {
104                         int curl_result = curl_message->data.result;
105                         slot = active_queue_head;
106                         while (slot != NULL &&
107                                slot->curl != curl_message->easy_handle)
108                                 slot = slot->next;
109                         if (slot != NULL) {
110                                 curl_multi_remove_handle(curlm, slot->curl);
111                                 slot->curl_result = curl_result;
112                                 finish_active_slot(slot);
113                         } else {
114                                 fprintf(stderr, "Received DONE message for unknown request!\n");
115                         }
116                 } else {
117                         fprintf(stderr, "Unknown CURL message received: %d\n",
118                                 (int)curl_message->msg);
119                 }
120                 curl_message = curl_multi_info_read(curlm, &num_messages);
121         }
123 #endif
125 static int http_options(const char *var, const char *value, void *cb)
127         if (!strcmp("http.sslverify", var)) {
128                 curl_ssl_verify = git_config_bool(var, value);
129                 return 0;
130         }
131         if (!strcmp("http.sslcert", var))
132                 return git_config_string(&ssl_cert, var, value);
133 #if LIBCURL_VERSION_NUM >= 0x070902
134         if (!strcmp("http.sslkey", var))
135                 return git_config_string(&ssl_key, var, value);
136 #endif
137 #if LIBCURL_VERSION_NUM >= 0x070908
138         if (!strcmp("http.sslcapath", var))
139                 return git_config_string(&ssl_capath, var, value);
140 #endif
141         if (!strcmp("http.sslcainfo", var))
142                 return git_config_string(&ssl_cainfo, var, value);
143         if (!strcmp("http.sslcertpasswordprotected", var)) {
144                 if (git_config_bool(var, value))
145                         ssl_cert_password_required = 1;
146                 return 0;
147         }
148 #ifdef USE_CURL_MULTI
149         if (!strcmp("http.maxrequests", var)) {
150                 max_requests = git_config_int(var, value);
151                 return 0;
152         }
153 #endif
154         if (!strcmp("http.lowspeedlimit", var)) {
155                 curl_low_speed_limit = (long)git_config_int(var, value);
156                 return 0;
157         }
158         if (!strcmp("http.lowspeedtime", var)) {
159                 curl_low_speed_time = (long)git_config_int(var, value);
160                 return 0;
161         }
163         if (!strcmp("http.noepsv", var)) {
164                 curl_ftp_no_epsv = git_config_bool(var, value);
165                 return 0;
166         }
167         if (!strcmp("http.proxy", var))
168                 return git_config_string(&curl_http_proxy, var, value);
170         /* Fall back on the default ones */
171         return git_default_config(var, value, cb);
174 static void init_curl_http_auth(CURL *result)
176         if (user_name) {
177                 struct strbuf up = STRBUF_INIT;
178                 if (!user_pass)
179                         user_pass = xstrdup(getpass("Password: "));
180                 strbuf_addf(&up, "%s:%s", user_name, user_pass);
181                 curl_easy_setopt(result, CURLOPT_USERPWD,
182                                  strbuf_detach(&up, NULL));
183         }
186 static int has_cert_password(void)
188         if (ssl_cert_password != NULL)
189                 return 1;
190         if (ssl_cert == NULL || ssl_cert_password_required != 1)
191                 return 0;
192         /* Only prompt the user once. */
193         ssl_cert_password_required = -1;
194         ssl_cert_password = getpass("Certificate Password: ");
195         if (ssl_cert_password != NULL) {
196                 ssl_cert_password = xstrdup(ssl_cert_password);
197                 return 1;
198         } else
199                 return 0;
202 static CURL *get_curl_handle(void)
204         CURL *result = curl_easy_init();
206         if (!curl_ssl_verify) {
207                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0);
208                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0);
209         } else {
210                 /* Verify authenticity of the peer's certificate */
211                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 1);
212                 /* The name in the cert must match whom we tried to connect */
213                 curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2);
214         }
216 #if LIBCURL_VERSION_NUM >= 0x070907
217         curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
218 #endif
220         init_curl_http_auth(result);
222         if (ssl_cert != NULL)
223                 curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
224         if (has_cert_password())
225                 curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password);
226 #if LIBCURL_VERSION_NUM >= 0x070902
227         if (ssl_key != NULL)
228                 curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
229 #endif
230 #if LIBCURL_VERSION_NUM >= 0x070908
231         if (ssl_capath != NULL)
232                 curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
233 #endif
234         if (ssl_cainfo != NULL)
235                 curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
236         curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
238         if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
239                 curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
240                                  curl_low_speed_limit);
241                 curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
242                                  curl_low_speed_time);
243         }
245         curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
247         if (getenv("GIT_CURL_VERBOSE"))
248                 curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
250         curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
252         if (curl_ftp_no_epsv)
253                 curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
255         if (curl_http_proxy)
256                 curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
258         return result;
261 static void http_auth_init(const char *url)
263         char *at, *colon, *cp, *slash;
264         int len;
266         cp = strstr(url, "://");
267         if (!cp)
268                 return;
270         /*
271          * Ok, the URL looks like "proto://something".  Which one?
272          * "proto://<user>:<pass>@<host>/...",
273          * "proto://<user>@<host>/...", or just
274          * "proto://<host>/..."?
275          */
276         cp += 3;
277         at = strchr(cp, '@');
278         colon = strchr(cp, ':');
279         slash = strchrnul(cp, '/');
280         if (!at || slash <= at)
281                 return; /* No credentials */
282         if (!colon || at <= colon) {
283                 /* Only username */
284                 len = at - cp;
285                 user_name = xmalloc(len + 1);
286                 memcpy(user_name, cp, len);
287                 user_name[len] = '\0';
288                 user_pass = NULL;
289         } else {
290                 len = colon - cp;
291                 user_name = xmalloc(len + 1);
292                 memcpy(user_name, cp, len);
293                 user_name[len] = '\0';
294                 len = at - (colon + 1);
295                 user_pass = xmalloc(len + 1);
296                 memcpy(user_pass, colon + 1, len);
297                 user_pass[len] = '\0';
298         }
301 static void set_from_env(const char **var, const char *envname)
303         const char *val = getenv(envname);
304         if (val)
305                 *var = val;
308 void http_init(struct remote *remote)
310         char *low_speed_limit;
311         char *low_speed_time;
313         git_config(http_options, NULL);
315         curl_global_init(CURL_GLOBAL_ALL);
317         if (remote && remote->http_proxy)
318                 curl_http_proxy = xstrdup(remote->http_proxy);
320         pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
322 #ifdef USE_CURL_MULTI
323         {
324                 char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
325                 if (http_max_requests != NULL)
326                         max_requests = atoi(http_max_requests);
327         }
329         curlm = curl_multi_init();
330         if (curlm == NULL) {
331                 fprintf(stderr, "Error creating curl multi handle.\n");
332                 exit(1);
333         }
334 #endif
336         if (getenv("GIT_SSL_NO_VERIFY"))
337                 curl_ssl_verify = 0;
339         set_from_env(&ssl_cert, "GIT_SSL_CERT");
340 #if LIBCURL_VERSION_NUM >= 0x070902
341         set_from_env(&ssl_key, "GIT_SSL_KEY");
342 #endif
343 #if LIBCURL_VERSION_NUM >= 0x070908
344         set_from_env(&ssl_capath, "GIT_SSL_CAPATH");
345 #endif
346         set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
348         low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
349         if (low_speed_limit != NULL)
350                 curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
351         low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
352         if (low_speed_time != NULL)
353                 curl_low_speed_time = strtol(low_speed_time, NULL, 10);
355         if (curl_ssl_verify == -1)
356                 curl_ssl_verify = 1;
358 #ifdef USE_CURL_MULTI
359         if (max_requests < 1)
360                 max_requests = DEFAULT_MAX_REQUESTS;
361 #endif
363         if (getenv("GIT_CURL_FTP_NO_EPSV"))
364                 curl_ftp_no_epsv = 1;
366         if (remote && remote->url && remote->url[0]) {
367                 http_auth_init(remote->url[0]);
368                 if (!ssl_cert_password_required &&
369                     getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
370                     !prefixcmp(remote->url[0], "https://"))
371                         ssl_cert_password_required = 1;
372         }
374 #ifndef NO_CURL_EASY_DUPHANDLE
375         curl_default = get_curl_handle();
376 #endif
379 void http_cleanup(void)
381         struct active_request_slot *slot = active_queue_head;
383         while (slot != NULL) {
384                 struct active_request_slot *next = slot->next;
385                 if (slot->curl != NULL) {
386 #ifdef USE_CURL_MULTI
387                         curl_multi_remove_handle(curlm, slot->curl);
388 #endif
389                         curl_easy_cleanup(slot->curl);
390                 }
391                 free(slot);
392                 slot = next;
393         }
394         active_queue_head = NULL;
396 #ifndef NO_CURL_EASY_DUPHANDLE
397         curl_easy_cleanup(curl_default);
398 #endif
400 #ifdef USE_CURL_MULTI
401         curl_multi_cleanup(curlm);
402 #endif
403         curl_global_cleanup();
405         curl_slist_free_all(pragma_header);
406         pragma_header = NULL;
408         if (curl_http_proxy) {
409                 free((void *)curl_http_proxy);
410                 curl_http_proxy = NULL;
411         }
413         if (ssl_cert_password != NULL) {
414                 memset(ssl_cert_password, 0, strlen(ssl_cert_password));
415                 free(ssl_cert_password);
416                 ssl_cert_password = NULL;
417         }
418         ssl_cert_password_required = 0;
421 struct active_request_slot *get_active_slot(void)
423         struct active_request_slot *slot = active_queue_head;
424         struct active_request_slot *newslot;
426 #ifdef USE_CURL_MULTI
427         int num_transfers;
429         /* Wait for a slot to open up if the queue is full */
430         while (active_requests >= max_requests) {
431                 curl_multi_perform(curlm, &num_transfers);
432                 if (num_transfers < active_requests)
433                         process_curl_messages();
434         }
435 #endif
437         while (slot != NULL && slot->in_use)
438                 slot = slot->next;
440         if (slot == NULL) {
441                 newslot = xmalloc(sizeof(*newslot));
442                 newslot->curl = NULL;
443                 newslot->in_use = 0;
444                 newslot->next = NULL;
446                 slot = active_queue_head;
447                 if (slot == NULL) {
448                         active_queue_head = newslot;
449                 } else {
450                         while (slot->next != NULL)
451                                 slot = slot->next;
452                         slot->next = newslot;
453                 }
454                 slot = newslot;
455         }
457         if (slot->curl == NULL) {
458 #ifdef NO_CURL_EASY_DUPHANDLE
459                 slot->curl = get_curl_handle();
460 #else
461                 slot->curl = curl_easy_duphandle(curl_default);
462 #endif
463         }
465         active_requests++;
466         slot->in_use = 1;
467         slot->local = NULL;
468         slot->results = NULL;
469         slot->finished = NULL;
470         slot->callback_data = NULL;
471         slot->callback_func = NULL;
472         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
473         curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
474         curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
475         curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
476         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
477         curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
478         curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
480         return slot;
483 int start_active_slot(struct active_request_slot *slot)
485 #ifdef USE_CURL_MULTI
486         CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
487         int num_transfers;
489         if (curlm_result != CURLM_OK &&
490             curlm_result != CURLM_CALL_MULTI_PERFORM) {
491                 active_requests--;
492                 slot->in_use = 0;
493                 return 0;
494         }
496         /*
497          * We know there must be something to do, since we just added
498          * something.
499          */
500         curl_multi_perform(curlm, &num_transfers);
501 #endif
502         return 1;
505 #ifdef USE_CURL_MULTI
506 struct fill_chain {
507         void *data;
508         int (*fill)(void *);
509         struct fill_chain *next;
510 };
512 static struct fill_chain *fill_cfg;
514 void add_fill_function(void *data, int (*fill)(void *))
516         struct fill_chain *new = xmalloc(sizeof(*new));
517         struct fill_chain **linkp = &fill_cfg;
518         new->data = data;
519         new->fill = fill;
520         new->next = NULL;
521         while (*linkp)
522                 linkp = &(*linkp)->next;
523         *linkp = new;
526 void fill_active_slots(void)
528         struct active_request_slot *slot = active_queue_head;
530         while (active_requests < max_requests) {
531                 struct fill_chain *fill;
532                 for (fill = fill_cfg; fill; fill = fill->next)
533                         if (fill->fill(fill->data))
534                                 break;
536                 if (!fill)
537                         break;
538         }
540         while (slot != NULL) {
541                 if (!slot->in_use && slot->curl != NULL) {
542                         curl_easy_cleanup(slot->curl);
543                         slot->curl = NULL;
544                 }
545                 slot = slot->next;
546         }
549 void step_active_slots(void)
551         int num_transfers;
552         CURLMcode curlm_result;
554         do {
555                 curlm_result = curl_multi_perform(curlm, &num_transfers);
556         } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
557         if (num_transfers < active_requests) {
558                 process_curl_messages();
559                 fill_active_slots();
560         }
562 #endif
564 void run_active_slot(struct active_request_slot *slot)
566 #ifdef USE_CURL_MULTI
567         long last_pos = 0;
568         long current_pos;
569         fd_set readfds;
570         fd_set writefds;
571         fd_set excfds;
572         int max_fd;
573         struct timeval select_timeout;
574         int finished = 0;
576         slot->finished = &finished;
577         while (!finished) {
578                 data_received = 0;
579                 step_active_slots();
581                 if (!data_received && slot->local != NULL) {
582                         current_pos = ftell(slot->local);
583                         if (current_pos > last_pos)
584                                 data_received++;
585                         last_pos = current_pos;
586                 }
588                 if (slot->in_use && !data_received) {
589                         max_fd = 0;
590                         FD_ZERO(&readfds);
591                         FD_ZERO(&writefds);
592                         FD_ZERO(&excfds);
593                         select_timeout.tv_sec = 0;
594                         select_timeout.tv_usec = 50000;
595                         select(max_fd, &readfds, &writefds,
596                                &excfds, &select_timeout);
597                 }
598         }
599 #else
600         while (slot->in_use) {
601                 slot->curl_result = curl_easy_perform(slot->curl);
602                 finish_active_slot(slot);
603         }
604 #endif
607 static void closedown_active_slot(struct active_request_slot *slot)
609         active_requests--;
610         slot->in_use = 0;
613 void release_active_slot(struct active_request_slot *slot)
615         closedown_active_slot(slot);
616         if (slot->curl) {
617 #ifdef USE_CURL_MULTI
618                 curl_multi_remove_handle(curlm, slot->curl);
619 #endif
620                 curl_easy_cleanup(slot->curl);
621                 slot->curl = NULL;
622         }
623 #ifdef USE_CURL_MULTI
624         fill_active_slots();
625 #endif
628 static void finish_active_slot(struct active_request_slot *slot)
630         closedown_active_slot(slot);
631         curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
633         if (slot->finished != NULL)
634                 (*slot->finished) = 1;
636         /* Store slot results so they can be read after the slot is reused */
637         if (slot->results != NULL) {
638                 slot->results->curl_result = slot->curl_result;
639                 slot->results->http_code = slot->http_code;
640         }
642         /* Run callback if appropriate */
643         if (slot->callback_func != NULL)
644                 slot->callback_func(slot->callback_data);
647 void finish_all_active_slots(void)
649         struct active_request_slot *slot = active_queue_head;
651         while (slot != NULL)
652                 if (slot->in_use) {
653                         run_active_slot(slot);
654                         slot = active_queue_head;
655                 } else {
656                         slot = slot->next;
657                 }
660 static inline int needs_quote(int ch)
662         if (((ch >= 'A') && (ch <= 'Z'))
663                         || ((ch >= 'a') && (ch <= 'z'))
664                         || ((ch >= '0') && (ch <= '9'))
665                         || (ch == '/')
666                         || (ch == '-')
667                         || (ch == '.'))
668                 return 0;
669         return 1;
672 static inline int hex(int v)
674         if (v < 10)
675                 return '0' + v;
676         else
677                 return 'A' + v - 10;
680 static char *quote_ref_url(const char *base, const char *ref)
682         struct strbuf buf = STRBUF_INIT;
683         const char *cp;
684         int ch;
686         strbuf_addstr(&buf, base);
687         if (buf.len && buf.buf[buf.len - 1] != '/' && *ref != '/')
688                 strbuf_addstr(&buf, "/");
690         for (cp = ref; (ch = *cp) != 0; cp++)
691                 if (needs_quote(ch))
692                         strbuf_addf(&buf, "%%%02x", ch);
693                 else
694                         strbuf_addch(&buf, *cp);
696         return strbuf_detach(&buf, NULL);
699 int http_fetch_ref(const char *base, struct ref *ref)
701         char *url;
702         struct strbuf buffer = STRBUF_INIT;
703         struct active_request_slot *slot;
704         struct slot_results results;
705         int ret;
707         url = quote_ref_url(base, ref->name);
708         slot = get_active_slot();
709         slot->results = &results;
710         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
711         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
712         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
713         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
714         if (start_active_slot(slot)) {
715                 run_active_slot(slot);
716                 if (results.curl_result == CURLE_OK) {
717                         strbuf_rtrim(&buffer);
718                         if (buffer.len == 40)
719                                 ret = get_sha1_hex(buffer.buf, ref->old_sha1);
720                         else if (!prefixcmp(buffer.buf, "ref: ")) {
721                                 ref->symref = xstrdup(buffer.buf + 5);
722                                 ret = 0;
723                         } else
724                                 ret = 1;
725                 } else {
726                         ret = error("Couldn't get %s for %s\n%s",
727                                     url, ref->name, curl_errorstr);
728                 }
729         } else {
730                 ret = error("Unable to start request");
731         }
733         strbuf_release(&buffer);
734         free(url);
735         return ret;