adding TCP STEALTH support to HTTP client (without integrity protection)
[oweals/gnunet.git] / src / transport / plugin_transport_http_client.c
1 /*
2      This file is part of GNUnet
3      (C) 2002-2014 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file transport/plugin_transport_http_client.c
23  * @brief HTTP/S client transport plugin
24  * @author Matthias Wachs
25  * @author Christian Grothoff
26  */
27
28 #if BUILD_HTTPS
29 #define PLUGIN_NAME "https_client"
30 #define HTTP_STAT_STR_CONNECTIONS "# HTTPS client connections"
31 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_https_client_init
32 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_https_client_done
33 #else
34 #define PLUGIN_NAME "http_client"
35 #define HTTP_STAT_STR_CONNECTIONS "# HTTP client connections"
36 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_http_client_init
37 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_http_client_done
38 #endif
39
40 #define VERBOSE_CURL GNUNET_NO
41
42 #define PUT_DISCONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
43
44 #define ENABLE_PUT GNUNET_YES
45 #define ENABLE_GET GNUNET_YES
46
47 #include "platform.h"
48 #include "gnunet_util_lib.h"
49 #include "gnunet_protocols.h"
50 #include "gnunet_transport_plugin.h"
51 #include "plugin_transport_http_common.h"
52 #include <curl/curl.h>
53
54
55 #define LOG(kind,...) GNUNET_log_from(kind, PLUGIN_NAME, __VA_ARGS__)
56
57 /**
58  * Encapsulation of all of the state of the plugin.
59  */
60 struct HTTP_Client_Plugin;
61
62 /**
63  * State of a HTTP PUT request
64  */
65 enum HTTP_PUT_REQUEST_STATE
66 {
67   /**
68    *  Just created, not yet connected
69    */
70   H_NOT_CONNECTED,
71
72   /**
73    *  Connected
74    */
75   H_CONNECTED,
76
77   /**
78    *  Paused, nothing to send
79    */
80   H_PAUSED,
81
82   /**
83    * Temporary disconnect in progress due to inactivity
84    */
85   H_TMP_DISCONNECTING,
86
87   /**
88    * Send request while temporary disconnect, reconnect
89    */
90   H_TMP_RECONNECT_REQUIRED,
91
92   /**
93    * Temporarily disconnected
94    */
95   H_TMP_DISCONNECTED,
96
97   /**
98    * Disconnected
99    */
100   H_DISCONNECTED
101 };
102
103 /**
104  *  Message to send using http
105  */
106 struct HTTP_Message
107 {
108   /**
109    * next pointer for double linked list
110    */
111   struct HTTP_Message *next;
112
113   /**
114    * previous pointer for double linked list
115    */
116   struct HTTP_Message *prev;
117
118   /**
119    * buffer containing data to send
120    */
121   char *buf;
122
123   /**
124    * Continuation function to call once the transmission buffer
125    * has again space available.  NULL if there is no
126    * continuation to call.
127    */
128   GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
129
130   /**
131    * Closure for @e transmit_cont.
132    */
133   void *transmit_cont_cls;
134
135   /**
136    * amount of data already sent
137    */
138   size_t pos;
139
140   /**
141    * buffer length
142    */
143   size_t size;
144
145 };
146
147
148 /**
149  * Session handle for HTTP(S) connections.
150  */
151 struct Session;
152
153
154 /**
155  * A request handle
156  *
157  */
158 struct RequestHandle
159 {
160   /**
161    * Current state of this request
162    */
163   enum HTTP_PUT_REQUEST_STATE state;
164
165   /**
166    * The curl easy handle
167    */
168   CURL *easyhandle;
169
170   /**
171    * The related session
172    */
173   struct Session *s;
174 };
175
176
177 /**
178  * Session handle for connections.
179  */
180 struct Session
181 {
182   /**
183    * The URL to connect to
184    */
185   char *url;
186
187   /**
188    * Address
189    */
190   struct GNUNET_HELLO_Address *address;
191
192   /**
193    * Pointer to the global plugin struct.
194    */
195   struct HTTP_Client_Plugin *plugin;
196
197   /**
198    * Handle for the HTTP PUT request.
199    */
200   struct RequestHandle put;
201
202   /**
203    * Handle for the HTTP GET request.
204    */
205   struct RequestHandle get;
206
207   /**
208    * next pointer for double linked list
209    */
210   struct HTTP_Message *msg_head;
211
212   /**
213    * previous pointer for double linked list
214    */
215   struct HTTP_Message *msg_tail;
216
217   /**
218    * Message stream tokenizer for incoming data
219    */
220   struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk;
221
222   /**
223    * Session timeout task
224    */
225   GNUNET_SCHEDULER_TaskIdentifier put_disconnect_task;
226
227   /**
228    * Session timeout task
229    */
230   GNUNET_SCHEDULER_TaskIdentifier timeout_task;
231
232   /**
233    * Task to wake up client receive handle when receiving is allowed again
234    */
235   GNUNET_SCHEDULER_TaskIdentifier recv_wakeup_task;
236
237   /**
238    * Absolute time when to receive data again.
239    * Used for receive throttling.
240    */
241   struct GNUNET_TIME_Absolute next_receive;
242
243   /**
244    * When does this session time out.
245    */
246   struct GNUNET_TIME_Absolute timeout;
247
248   /**
249    * Number of bytes waiting for transmission to this peer.
250    */
251   unsigned long long bytes_in_queue;
252
253   /**
254    * Outbound overhead due to HTTP connection
255    * Add to next message of this session when calling callback
256    */
257   size_t overhead;
258
259   /**
260    * Number of messages waiting for transmission to this peer.
261    */
262   unsigned int msgs_in_queue;
263
264   /**
265    * ATS network type in NBO
266    */
267   uint32_t ats_address_network_type;
268 };
269
270
271 /**
272  * Encapsulation of all of the state of the plugin.
273  */
274 struct HTTP_Client_Plugin
275 {
276   /**
277    * Our environment.
278    */
279   struct GNUNET_TRANSPORT_PluginEnvironment *env;
280
281   /**
282    * Open sessions.
283    */
284   struct GNUNET_CONTAINER_MultiPeerMap *sessions;
285
286   /**
287    * Function to call about session status changes.
288    */
289   GNUNET_TRANSPORT_SessionInfoCallback sic;
290
291   /**
292    * Closure for @e sic.
293    */
294   void *sic_cls;
295
296   /**
297    * Plugin name
298    */
299   char *name;
300
301   /**
302    * Protocol
303    */
304   char *protocol;
305
306   /**
307    * Proxy configuration: hostname or ip of the proxy server
308    */
309   char *proxy_hostname;
310
311   /**
312    * Username for the proxy server
313    */
314   char *proxy_username;
315
316   /**
317    * Password for the proxy server
318    */
319   char *proxy_password;
320
321   /**
322    * cURL Multihandle
323    */
324   CURLM *curl_multi_handle;
325
326   /**
327    * curl perform task
328    */
329   GNUNET_SCHEDULER_TaskIdentifier client_perform_task;
330
331   /**
332    * Type of proxy server:
333    *
334    * Valid values as supported by curl:
335    * CURLPROXY_HTTP, CURLPROXY_HTTP_1_0 CURLPROXY_SOCKS4, CURLPROXY_SOCKS5,
336    * CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME
337    */
338   curl_proxytype proxytype;
339
340   /**
341    * Use proxy tunneling:
342    * Tunnel all operations through a given HTTP instead of have the proxy
343    * evaluate the HTTP request
344    *
345    * Default: #GNUNET_NO, #GNUNET_YES experimental
346    */
347   int proxy_use_httpproxytunnel;
348
349   /**
350    * My options to be included in the address
351    */
352   uint32_t options;
353
354   /**
355    * Maximum number of sockets the plugin can use
356    * Each http connections are two requests
357    */
358   unsigned int max_requests;
359
360   /**
361    * Current number of sockets the plugin can use
362    * Each http connections are two requests
363    */
364   unsigned int cur_requests;
365
366   /**
367    * Last used unique HTTP connection tag
368    */
369   uint32_t last_tag;
370
371   /**
372    * use IPv6
373    */
374   uint16_t use_ipv6;
375
376   /**
377    * use IPv4
378    */
379   uint16_t use_ipv4;
380
381   /**
382    * Should we emulate an XHR client for testing?
383    */
384   int emulate_xhr;
385 };
386
387
388 /**
389  * Disconnect a session
390  *
391  * @param cls the `struct HTTP_Client_Plugin *`
392  * @param s session
393  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
394  */
395 static int
396 http_client_plugin_session_disconnect (void *cls, struct Session *s);
397
398
399 /**
400  * If a session monitor is attached, notify it about the new
401  * session state.
402  *
403  * @param plugin our plugin
404  * @param session session that changed state
405  * @param state new state of the session
406  */
407 static void
408 notify_session_monitor (struct HTTP_Client_Plugin *plugin,
409                         struct Session *session,
410                         enum GNUNET_TRANSPORT_SessionState state)
411 {
412   struct GNUNET_TRANSPORT_SessionInfo info;
413
414   if (NULL == plugin->sic)
415     return;
416   memset (&info, 0, sizeof (info));
417   info.state = state;
418   info.is_inbound = GNUNET_NO;
419   info.num_msg_pending = session->msgs_in_queue;
420   info.num_bytes_pending = session->bytes_in_queue;
421   info.receive_delay = session->next_receive;
422   info.session_timeout = session->timeout;
423   info.address = session->address;
424   plugin->sic (plugin->sic_cls,
425                session,
426                &info);
427 }
428
429
430 /**
431  * Delete session @a s.
432  *
433  * @param s the session to delete
434  */
435 static void
436 client_delete_session (struct Session *s)
437 {
438   struct HTTP_Client_Plugin *plugin = s->plugin;
439   struct HTTP_Message *pos;
440   struct HTTP_Message *next;
441   CURLMcode mret;
442
443   if (GNUNET_SCHEDULER_NO_TASK != s->timeout_task)
444   {
445     GNUNET_SCHEDULER_cancel (s->timeout_task);
446     s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
447     s->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
448   }
449   if (GNUNET_SCHEDULER_NO_TASK != s->put_disconnect_task)
450   {
451     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
452     s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
453   }
454   if (GNUNET_SCHEDULER_NO_TASK != s->recv_wakeup_task)
455   {
456     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
457     s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
458   }
459   GNUNET_assert (GNUNET_OK ==
460                  GNUNET_CONTAINER_multipeermap_remove (plugin->sessions,
461                                                        &s->address->peer,
462                                                        s));
463   if (NULL != s->put.easyhandle)
464   {
465     LOG (GNUNET_ERROR_TYPE_DEBUG,
466          "Session %p/request %p: disconnecting PUT request to peer `%s'\n",
467          s,
468          s->put.easyhandle,
469          GNUNET_i2s (&s->address->peer));
470
471     /* remove curl handle from multi handle */
472     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
473                                      s->put.easyhandle);
474     GNUNET_break (CURLM_OK == mret);
475     curl_easy_cleanup (s->put.easyhandle);
476     GNUNET_assert (plugin->cur_requests > 0);
477     plugin->cur_requests--;
478     s->put.easyhandle = NULL;
479   }
480   if (NULL != s->get.easyhandle)
481   {
482     LOG (GNUNET_ERROR_TYPE_DEBUG,
483          "Session %p/request %p: disconnecting GET request to peer `%s'\n",
484          s, s->get.easyhandle,
485          GNUNET_i2s (&s->address->peer));
486     /* remove curl handle from multi handle */
487     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
488                                      s->get.easyhandle);
489     GNUNET_break (CURLM_OK == mret);
490     curl_easy_cleanup (s->get.easyhandle);
491     GNUNET_assert (plugin->cur_requests > 0);
492     plugin->cur_requests--;
493     s->get.easyhandle = NULL;
494   }
495
496   GNUNET_STATISTICS_set (plugin->env->stats,
497                          HTTP_STAT_STR_CONNECTIONS,
498                          plugin->cur_requests,
499                          GNUNET_NO);
500   next = s->msg_head;
501   while (NULL != (pos = next))
502   {
503     next = pos->next;
504     GNUNET_CONTAINER_DLL_remove (s->msg_head,
505                                  s->msg_tail,
506                                  pos);
507     GNUNET_assert (0 < s->msgs_in_queue);
508     s->msgs_in_queue--;
509     GNUNET_assert (pos->size <= s->bytes_in_queue);
510     s->bytes_in_queue -= pos->size;
511     if (NULL != pos->transmit_cont)
512       pos->transmit_cont (pos->transmit_cont_cls,
513                           &s->address->peer,
514                           GNUNET_SYSERR,
515                           pos->size,
516                           pos->pos + s->overhead);
517     s->overhead = 0;
518     GNUNET_free (pos);
519   }
520   GNUNET_assert (0 == s->msgs_in_queue);
521   GNUNET_assert (0 == s->bytes_in_queue);
522   notify_session_monitor (plugin,
523                           s,
524                           GNUNET_TRANSPORT_SS_DONE);
525   if (NULL != s->msg_tk)
526   {
527     GNUNET_SERVER_mst_destroy (s->msg_tk);
528     s->msg_tk = NULL;
529   }
530   GNUNET_HELLO_address_free (s->address);
531   GNUNET_free (s->url);
532   GNUNET_free (s);
533 }
534
535
536 /**
537  * Increment session timeout due to activity for session @a s.
538  *
539  * @param s the session
540  */
541 static void
542 client_reschedule_session_timeout (struct Session *s)
543 {
544   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK != s->timeout_task);
545   s->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT);
546 }
547
548
549 /**
550  * Task performing curl operations
551  *
552  * @param cls plugin as closure
553  * @param tc gnunet scheduler task context
554  */
555 static void
556 client_run (void *cls,
557             const struct GNUNET_SCHEDULER_TaskContext *tc);
558
559
560 /**
561  * Function setting up file descriptors and scheduling task to run
562  *
563  * @param plugin the plugin as closure
564  * @param now schedule task in 1ms, regardless of what curl may say
565  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
566  */
567 static int
568 client_schedule (struct HTTP_Client_Plugin *plugin,
569                  int now)
570 {
571   fd_set rs;
572   fd_set ws;
573   fd_set es;
574   int max;
575   struct GNUNET_NETWORK_FDSet *grs;
576   struct GNUNET_NETWORK_FDSet *gws;
577   long to;
578   CURLMcode mret;
579   struct GNUNET_TIME_Relative timeout;
580
581   /* Cancel previous scheduled task */
582   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
583   {
584     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
585     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
586   }
587   max = -1;
588   FD_ZERO (&rs);
589   FD_ZERO (&ws);
590   FD_ZERO (&es);
591   mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
592   if (mret != CURLM_OK)
593   {
594     LOG (GNUNET_ERROR_TYPE_ERROR,
595          _("%s failed at %s:%d: `%s'\n"),
596          "curl_multi_fdset",
597          __FILE__,
598          __LINE__,
599          curl_multi_strerror (mret));
600     return GNUNET_SYSERR;
601   }
602   mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
603   if (-1 == to)
604     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
605   else
606     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
607   if (now == GNUNET_YES)
608     timeout = GNUNET_TIME_UNIT_MILLISECONDS;
609
610   if (CURLM_OK != mret)
611   {
612     LOG (GNUNET_ERROR_TYPE_ERROR,
613                 _("%s failed at %s:%d: `%s'\n"),
614                 "curl_multi_timeout", __FILE__, __LINE__,
615                 curl_multi_strerror (mret));
616     return GNUNET_SYSERR;
617   }
618
619   grs = GNUNET_NETWORK_fdset_create ();
620   gws = GNUNET_NETWORK_fdset_create ();
621   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
622   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
623
624   /* Schedule task to run when select is ready to read or write */
625   plugin->client_perform_task =
626       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
627                                    timeout, grs, gws,
628                                    &client_run, plugin);
629   GNUNET_NETWORK_fdset_destroy (gws);
630   GNUNET_NETWORK_fdset_destroy (grs);
631   return GNUNET_OK;
632 }
633
634
635 #if VERBOSE_CURL
636 /**
637  * Loggging function
638  *
639  * @param curl the curl easy handle
640  * @param type message type
641  * @param data data to log, NOT a 0-terminated string
642  * @param size data length
643  * @param cls the closure
644  * @return always 0
645  */
646 static int
647 client_log (CURL *curl,
648             curl_infotype type,
649             const char *data,
650             size_t size,
651             void *cls)
652 {
653   struct RequestHandle *ch = cls;
654   const char *ttype = "UNSPECIFIED";
655   char text[size + 2];
656
657   if (! ((CURLINFO_TEXT == type) ||
658          (CURLINFO_HEADER_IN == type) ||
659          (CURLINFO_HEADER_OUT == type)))
660     return 0;
661   switch (type)
662   {
663   case CURLINFO_TEXT:
664     ttype = "TEXT";
665     break;
666   case CURLINFO_HEADER_IN:
667     ttype = "HEADER_IN";
668     break;
669   case CURLINFO_HEADER_OUT:
670     ttype = "HEADER_OUT";
671     /* Overhead*/
672     GNUNET_assert (NULL != ch);
673     GNUNET_assert (NULL != ch->easyhandle);
674     GNUNET_assert (NULL != ch->s);
675     ch->s->overhead += size;
676     break;
677   default:
678     ttype = "UNSPECIFIED";
679     break;
680   }
681   memcpy (text, data, size);
682   if (text[size - 1] == '\n')
683   {
684     text[size] = '\0';
685   }
686   else
687   {
688     text[size] = '\n';
689     text[size + 1] = '\0';
690   }
691   LOG (GNUNET_ERROR_TYPE_DEBUG,
692        "Request %p %s: %s",
693        ch->easyhandle,
694        ttype,
695        text);
696   return 0;
697 }
698 #endif
699
700 /**
701  * Connect GET request
702  *
703  * @param s the session to connect
704  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
705  */
706 static int
707 client_connect_get (struct Session *s);
708
709
710 /**
711  * Connect a HTTP put request
712  *
713  * @param s the session to connect
714  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for success
715  */
716 static int
717 client_connect_put (struct Session *s);
718
719
720 /**
721  * Function that can be used by the transport service to transmit
722  * a message using the plugin.   Note that in the case of a
723  * peer disconnecting, the continuation MUST be called
724  * prior to the disconnect notification itself.  This function
725  * will be called with this peer's HELLO message to initiate
726  * a fresh connection to another peer.
727  *
728  * @param cls closure
729  * @param s which session must be used
730  * @param msgbuf the message to transmit
731  * @param msgbuf_size number of bytes in @a msgbuf
732  * @param priority how important is the message (most plugins will
733  *                 ignore message priority and just FIFO)
734  * @param to how long to wait at most for the transmission (does not
735  *                require plugins to discard the message after the timeout,
736  *                just advisory for the desired delay; most plugins will ignore
737  *                this as well)
738  * @param cont continuation to call once the message has
739  *        been transmitted (or if the transport is ready
740  *        for the next transmission call; or if the
741  *        peer disconnected...); can be NULL
742  * @param cont_cls closure for @a cont
743  * @return number of bytes used (on the physical network, with overheads);
744  *         -1 on hard errors (i.e. address invalid); 0 is a legal value
745  *         and does NOT mean that the message was not transmitted (DV)
746  */
747 static ssize_t
748 http_client_plugin_send (void *cls,
749                          struct Session *s,
750                          const char *msgbuf,
751                          size_t msgbuf_size,
752                          unsigned int priority,
753                          struct GNUNET_TIME_Relative to,
754                          GNUNET_TRANSPORT_TransmitContinuation cont,
755                          void *cont_cls)
756 {
757   struct HTTP_Client_Plugin *plugin = cls;
758   struct HTTP_Message *msg;
759   char *stat_txt;
760
761   LOG (GNUNET_ERROR_TYPE_DEBUG,
762        "Session %p/request %p: Sending message with %u to peer `%s' \n",
763        s,
764        s->put.easyhandle,
765        msgbuf_size,
766        GNUNET_i2s (&s->address->peer));
767
768   /* create new message and schedule */
769   msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
770   msg->size = msgbuf_size;
771   msg->buf = (char *) &msg[1];
772   msg->transmit_cont = cont;
773   msg->transmit_cont_cls = cont_cls;
774   memcpy (msg->buf,
775           msgbuf,
776           msgbuf_size);
777   GNUNET_CONTAINER_DLL_insert_tail (s->msg_head,
778                                     s->msg_tail,
779                                     msg);
780   s->msgs_in_queue++;
781   s->bytes_in_queue += msg->size;
782
783   GNUNET_asprintf (&stat_txt,
784                    "# bytes currently in %s_client buffers",
785                    plugin->protocol);
786   GNUNET_STATISTICS_update (plugin->env->stats,
787                             stat_txt, msgbuf_size, GNUNET_NO);
788   GNUNET_free (stat_txt);
789   notify_session_monitor (plugin,
790                           s,
791                           GNUNET_TRANSPORT_SS_UPDATE);
792   if (H_TMP_DISCONNECTING == s->put.state)
793   {
794     /* PUT request is currently getting disconnected */
795     s->put.state = H_TMP_RECONNECT_REQUIRED;
796     LOG (GNUNET_ERROR_TYPE_DEBUG,
797          "Session %p/request %p: currently disconnecting, reconnecting immediately\n",
798          s,
799          s->put.easyhandle);
800     return msgbuf_size;
801   }
802   if (H_PAUSED == s->put.state)
803   {
804     /* PUT request was paused, unpause */
805     GNUNET_assert (s->put_disconnect_task != GNUNET_SCHEDULER_NO_TASK);
806     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
807     s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
808     LOG (GNUNET_ERROR_TYPE_DEBUG,
809          "Session %p/request %p: unpausing request\n",
810          s, s->put.easyhandle);
811     s->put.state = H_CONNECTED;
812     if (NULL != s->put.easyhandle)
813       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
814   }
815   else if (H_TMP_DISCONNECTED == s->put.state)
816   {
817     /* PUT request was disconnected, reconnect */
818     LOG (GNUNET_ERROR_TYPE_DEBUG, "Session %p: Reconnecting PUT request\n", s);
819     GNUNET_break (NULL == s->put.easyhandle);
820     if (GNUNET_SYSERR == client_connect_put (s))
821     {
822       /* Could not reconnect */
823       http_client_plugin_session_disconnect (plugin, s);
824       return GNUNET_SYSERR;
825     }
826   }
827   client_schedule (s->plugin, GNUNET_YES);
828   return msgbuf_size;
829 }
830
831
832 /**
833  * Disconnect a session
834  *
835  * @param cls the `struct HTTP_Client_Plugin *`
836  * @param s session
837  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
838  */
839 static int
840 http_client_plugin_session_disconnect (void *cls,
841                                        struct Session *s)
842 {
843   struct HTTP_Client_Plugin *plugin = cls;
844
845   LOG (GNUNET_ERROR_TYPE_DEBUG,
846        "Session %p: notifying transport about ending session\n",
847        s);
848   plugin->env->session_end (plugin->env->cls,
849                             s->address,
850                             s);
851   client_delete_session (s);
852
853   /* Re-schedule since handles have changed */
854   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
855   {
856     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
857     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
858   }
859   client_schedule (plugin, GNUNET_YES);
860
861   return GNUNET_OK;
862 }
863
864
865 /**
866  * Function that is called to get the keepalive factor.
867  * #GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to
868  * calculate the interval between keepalive packets.
869  *
870  * @param cls closure with the `struct Plugin`
871  * @return keepalive factor
872  */
873 static unsigned int
874 http_client_query_keepalive_factor (void *cls)
875 {
876   return 3;
877 }
878
879
880 /**
881  * Callback to destroys all sessions on exit.
882  *
883  * @param cls the `struct HTTP_Client_Plugin *`
884  * @param peer identity of the peer
885  * @param value the `struct Session *`
886  * @return #GNUNET_OK (continue iterating)
887  */
888 static int
889 destroy_session_cb (void *cls,
890                     const struct GNUNET_PeerIdentity *peer,
891                     void *value)
892 {
893   struct HTTP_Client_Plugin *plugin = cls;
894   struct Session *session = value;
895
896   http_client_plugin_session_disconnect (plugin, session);
897   return GNUNET_OK;
898 }
899
900
901 /**
902  * Function that can be used to force the plugin to disconnect
903  * from the given peer and cancel all previous transmissions
904  * (and their continuationc).
905  *
906  * @param cls closure
907  * @param target peer from which to disconnect
908  */
909 static void
910 http_client_plugin_peer_disconnect (void *cls,
911                                     const struct GNUNET_PeerIdentity *target)
912 {
913   struct HTTP_Client_Plugin *plugin = cls;
914
915   LOG (GNUNET_ERROR_TYPE_DEBUG,
916        "Transport tells me to disconnect `%s'\n",
917        GNUNET_i2s (target));
918   GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessions,
919                                               target,
920                                               &destroy_session_cb,
921                                               plugin);
922 }
923
924
925 /**
926  * Closure for #session_lookup_client_by_address().
927  */
928 struct SessionClientCtx
929 {
930   /**
931    * Address we are looking for.
932    */
933   const struct GNUNET_HELLO_Address *address;
934
935   /**
936    * Session that was found.
937    */
938   struct Session *ret;
939 };
940
941
942 /**
943  * Locate the seession object for a given address.
944  *
945  * @param cls the `struct SessionClientCtx *`
946  * @param key peer identity
947  * @param value the `struct Session` to check
948  * @return #GNUNET_NO if found, #GNUNET_OK if not
949  */
950 static int
951 session_lookup_client_by_address (void *cls,
952                                   const struct GNUNET_PeerIdentity *key,
953                                   void *value)
954 {
955   struct SessionClientCtx *sc_ctx = cls;
956   struct Session *s = value;
957
958   if (0 == GNUNET_HELLO_address_cmp (sc_ctx->address,
959                                      s->address))
960   {
961     sc_ctx->ret = s;
962     return GNUNET_NO;
963   }
964   return GNUNET_YES;
965 }
966
967
968 /**
969  * Check if a sessions exists for an specific address
970  *
971  * @param plugin the plugin
972  * @param address the address
973  * @return the session or NULL
974  */
975 static struct Session *
976 client_lookup_session (struct HTTP_Client_Plugin *plugin,
977                        const struct GNUNET_HELLO_Address *address)
978 {
979   struct SessionClientCtx sc_ctx;
980
981   sc_ctx.address = address;
982   sc_ctx.ret = NULL;
983   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
984                                          &session_lookup_client_by_address,
985                                          &sc_ctx);
986   return sc_ctx.ret;
987 }
988
989
990 /**
991  * When we have nothing to transmit, we pause the HTTP PUT
992  * after a while (so that gnurl stops asking).  This task
993  * is the delayed task that actually disconnects the PUT.
994  *
995  * @param cls the `struct Session *` with the put
996  * @param tc scheduler context
997  */
998 static void
999 client_put_disconnect (void *cls,
1000                        const struct GNUNET_SCHEDULER_TaskContext *tc)
1001 {
1002   struct Session *s = cls;
1003
1004   s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
1005   LOG (GNUNET_ERROR_TYPE_DEBUG,
1006        "Session %p/request %p: will be disconnected due to no activity\n",
1007        s, s->put.easyhandle);
1008   s->put.state = H_TMP_DISCONNECTING;
1009   if (NULL != s->put.easyhandle)
1010     curl_easy_pause (s->put.easyhandle,
1011                      CURLPAUSE_CONT);
1012   client_schedule (s->plugin, GNUNET_YES);
1013 }
1014
1015
1016 /**
1017  * Callback method used with libcurl
1018  * Method is called when libcurl needs to read data during sending
1019  *
1020  * @param stream pointer where to write data
1021  * @param size size of an individual element
1022  * @param nmemb count of elements that can be written to the buffer
1023  * @param cls our `struct Session`
1024  * @return bytes written to stream, returning 0 will terminate request!
1025  */
1026 static size_t
1027 client_send_cb (void *stream,
1028                 size_t size,
1029                 size_t nmemb,
1030                 void *cls)
1031 {
1032   struct Session *s = cls;
1033   struct HTTP_Client_Plugin *plugin = s->plugin;
1034   struct HTTP_Message *msg = s->msg_head;
1035   size_t len;
1036   char *stat_txt;
1037
1038   if (H_TMP_DISCONNECTING == s->put.state)
1039   {
1040     LOG (GNUNET_ERROR_TYPE_DEBUG,
1041          "Session %p/request %p: disconnect due to inactivity\n",
1042          s, s->put.easyhandle);
1043     return 0;
1044   }
1045
1046   if (NULL == msg)
1047   {
1048     if (GNUNET_YES == plugin->emulate_xhr)
1049     {
1050       LOG (GNUNET_ERROR_TYPE_DEBUG,
1051            "Session %p/request %p: PUT request finished\n",
1052            s,
1053            s->put.easyhandle);
1054       s->put.state = H_TMP_DISCONNECTING;
1055       return 0;
1056     }
1057
1058     /* We have nothing to send, so pause PUT request */
1059     LOG (GNUNET_ERROR_TYPE_DEBUG,
1060          "Session %p/request %p: nothing to send, suspending\n",
1061          s,
1062          s->put.easyhandle);
1063     s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT,
1064         &client_put_disconnect, s);
1065     s->put.state = H_PAUSED;
1066     return CURL_READFUNC_PAUSE;
1067   }
1068   /* data to send */
1069   GNUNET_assert (msg->pos < msg->size);
1070   /* calculate how much fits in buffer */
1071   len = GNUNET_MIN (msg->size - msg->pos,
1072                     size * nmemb);
1073   memcpy (stream, &msg->buf[msg->pos], len);
1074   msg->pos += len;
1075   if (msg->pos == msg->size)
1076   {
1077     LOG (GNUNET_ERROR_TYPE_DEBUG,
1078          "Session %p/request %p: sent message with %u bytes sent, removing message from queue\n",
1079          s,
1080          s->put.easyhandle,
1081          msg->size,
1082          msg->pos);
1083     /* Calling transmit continuation  */
1084     GNUNET_CONTAINER_DLL_remove (s->msg_head,
1085                                  s->msg_tail,
1086                                  msg);
1087     GNUNET_assert (0 < s->msgs_in_queue);
1088     s->msgs_in_queue--;
1089     GNUNET_assert (msg->size <= s->bytes_in_queue);
1090     s->bytes_in_queue -= msg->size;
1091     if (NULL != msg->transmit_cont)
1092       msg->transmit_cont (msg->transmit_cont_cls,
1093                           &s->address->peer,
1094                           GNUNET_OK,
1095                           msg->size,
1096                           msg->size + s->overhead);
1097     s->overhead = 0;
1098     GNUNET_free (msg);
1099   }
1100   notify_session_monitor (plugin,
1101                           s,
1102                           GNUNET_TRANSPORT_SS_UPDATE);
1103   GNUNET_asprintf (&stat_txt,
1104                    "# bytes currently in %s_client buffers",
1105                    plugin->protocol);
1106   GNUNET_STATISTICS_update (plugin->env->stats,
1107                             stat_txt,
1108                             - len,
1109                             GNUNET_NO);
1110   GNUNET_free (stat_txt);
1111   GNUNET_asprintf (&stat_txt,
1112                    "# bytes transmitted via %s_client",
1113                    plugin->protocol);
1114   GNUNET_STATISTICS_update (plugin->env->stats,
1115                             stat_txt,
1116                             len,
1117                             GNUNET_NO);
1118   GNUNET_free (stat_txt);
1119   return len;
1120 }
1121
1122
1123 /**
1124  * Wake up a curl handle which was suspended
1125  *
1126  * @param cls the session
1127  * @param tc task context
1128  */
1129 static void
1130 client_wake_up (void *cls,
1131                 const struct GNUNET_SCHEDULER_TaskContext *tc)
1132 {
1133   struct Session *s = cls;
1134
1135   s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
1136   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1137     return;
1138   LOG (GNUNET_ERROR_TYPE_DEBUG,
1139        "Session %p/request %p: Waking up GET handle\n",
1140        s, s->get.easyhandle);
1141   if (H_PAUSED == s->put.state)
1142   {
1143     /* PUT request was paused, unpause */
1144     GNUNET_assert (s->put_disconnect_task != GNUNET_SCHEDULER_NO_TASK);
1145     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
1146     s->put_disconnect_task = GNUNET_SCHEDULER_NO_TASK;
1147     s->put.state = H_CONNECTED;
1148     if (NULL != s->put.easyhandle)
1149       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
1150   }
1151   if (NULL != s->get.easyhandle)
1152     curl_easy_pause (s->get.easyhandle, CURLPAUSE_CONT);
1153 }
1154
1155
1156 /**
1157  * Callback for message stream tokenizer
1158  *
1159  * @param cls the session
1160  * @param client not used
1161  * @param message the message received
1162  * @return always #GNUNET_OK
1163  */
1164 static int
1165 client_receive_mst_cb (void *cls,
1166                        void *client,
1167                        const struct GNUNET_MessageHeader *message)
1168 {
1169   struct Session *s = cls;
1170   struct HTTP_Client_Plugin *plugin;
1171   struct GNUNET_TIME_Relative delay;
1172   struct GNUNET_ATS_Information atsi;
1173   char *stat_txt;
1174
1175   plugin = s->plugin;
1176   atsi.type = htonl (GNUNET_ATS_NETWORK_TYPE);
1177   atsi.value = s->ats_address_network_type;
1178   GNUNET_break (s->ats_address_network_type != ntohl (GNUNET_ATS_NET_UNSPECIFIED));
1179
1180   delay = s->plugin->env->receive (plugin->env->cls,
1181                                    s->address,
1182                                    s,
1183                                    message);
1184   plugin->env->update_address_metrics (plugin->env->cls,
1185                                        s->address,
1186                                        s,
1187                                        &atsi, 1);
1188
1189   GNUNET_asprintf (&stat_txt,
1190                    "# bytes received via %s_client",
1191                    plugin->protocol);
1192   GNUNET_STATISTICS_update (plugin->env->stats,
1193                             stat_txt,
1194                             ntohs (message->size),
1195                             GNUNET_NO);
1196   GNUNET_free (stat_txt);
1197
1198   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
1199   if (GNUNET_TIME_absolute_get ().abs_value_us < s->next_receive.abs_value_us)
1200   {
1201     LOG (GNUNET_ERROR_TYPE_DEBUG,
1202          "Client: peer `%s' address `%s' next read delayed for %s\n",
1203          GNUNET_i2s (&s->address->peer),
1204          http_common_plugin_address_to_string (s->plugin->protocol,
1205                                                s->address->address,
1206                                                s->address->address_length),
1207          GNUNET_STRINGS_relative_time_to_string (delay,
1208                                                  GNUNET_YES));
1209   }
1210   client_reschedule_session_timeout (s);
1211   return GNUNET_OK;
1212 }
1213
1214
1215 /**
1216  * Callback method used with libcurl when data for a PUT request are
1217  * received.  We do not expect data here, so we just discard it.
1218  *
1219  * @param stream pointer where to write data
1220  * @param size size of an individual element
1221  * @param nmemb count of elements that can be written to the buffer
1222  * @param cls destination pointer, passed to the libcurl handle
1223  * @return bytes read from stream
1224  */
1225 static size_t
1226 client_receive_put (void *stream,
1227                     size_t size,
1228                     size_t nmemb,
1229                     void *cls)
1230 {
1231   return size * nmemb;
1232 }
1233
1234
1235 /**
1236  * Callback method used with libcurl when data for a GET request are
1237  * received. Forward to MST
1238  *
1239  * @param stream pointer where to write data
1240  * @param size size of an individual element
1241  * @param nmemb count of elements that can be written to the buffer
1242  * @param cls destination pointer, passed to the libcurl handle
1243  * @return bytes read from stream
1244  */
1245 static size_t
1246 client_receive (void *stream,
1247                 size_t size,
1248                 size_t nmemb,
1249                 void *cls)
1250 {
1251   struct Session *s = cls;
1252   struct GNUNET_TIME_Absolute now;
1253   size_t len = size * nmemb;
1254
1255   LOG (GNUNET_ERROR_TYPE_DEBUG,
1256        "Session %p / request %p: Received %u bytes from peer `%s'\n",
1257        s,
1258        s->get.easyhandle,
1259        len,
1260        GNUNET_i2s (&s->address->peer));
1261   now = GNUNET_TIME_absolute_get ();
1262   if (now.abs_value_us < s->next_receive.abs_value_us)
1263   {
1264     struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
1265     struct GNUNET_TIME_Relative delta
1266       = GNUNET_TIME_absolute_get_difference (now, s->next_receive);
1267
1268     LOG (GNUNET_ERROR_TYPE_DEBUG,
1269          "Session %p / request %p: No inbound bandwidth available! Next read was delayed for %s\n",
1270          s,
1271          s->get.easyhandle,
1272          GNUNET_STRINGS_relative_time_to_string (delta,
1273                                                  GNUNET_YES));
1274     if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
1275     {
1276       GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
1277       s->recv_wakeup_task = GNUNET_SCHEDULER_NO_TASK;
1278     }
1279     s->recv_wakeup_task
1280       = GNUNET_SCHEDULER_add_delayed (delta,
1281                                       &client_wake_up,
1282                                       s);
1283     return CURL_WRITEFUNC_PAUSE;
1284   }
1285   if (NULL == s->msg_tk)
1286     s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb,
1287                                           s);
1288   GNUNET_SERVER_mst_receive (s->msg_tk,
1289                              s,
1290                              stream,
1291                              len,
1292                              GNUNET_NO,
1293                              GNUNET_NO);
1294   return len;
1295 }
1296
1297
1298 /**
1299  * Task performing curl operations
1300  *
1301  * @param cls plugin as closure
1302  * @param tc scheduler task context
1303  */
1304 static void
1305 client_run (void *cls,
1306             const struct GNUNET_SCHEDULER_TaskContext *tc)
1307 {
1308   struct HTTP_Client_Plugin *plugin = cls;
1309   int running;
1310   long http_statuscode;
1311   CURLMcode mret;
1312   CURLMsg *msg;
1313   int put_request; /* GNUNET_YES if easy handle is put, GNUNET_NO for get */
1314   int msgs_left;
1315
1316   plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1317   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1318     return;
1319
1320   /* While data are available or timeouts occured */
1321   do
1322   {
1323     running = 0;
1324     /* Perform operations for all handles */
1325     mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1326
1327     /* Get additional information for all handles */
1328     while (NULL != (msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1329     {
1330       CURL *easy_h = msg->easy_handle;
1331       struct Session *s = NULL;
1332       char *d = NULL; /* curl requires 'd' to be a 'char *' */
1333
1334       GNUNET_assert (NULL != easy_h);
1335
1336       /* Obtain session from easy handle */
1337       GNUNET_assert (CURLE_OK == curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1338       s = (struct Session *) d;
1339       GNUNET_assert (NULL != s);
1340
1341       if (msg->msg != CURLMSG_DONE)
1342         continue; /* This should not happen */
1343
1344       /* Get HTTP response code */
1345       GNUNET_break (CURLE_OK == curl_easy_getinfo (easy_h,
1346           CURLINFO_RESPONSE_CODE, &http_statuscode));
1347
1348       if (easy_h == s->put.easyhandle)
1349         put_request = GNUNET_YES;
1350       else
1351         put_request = GNUNET_NO;
1352
1353       /* Log status of terminated request */
1354       if  ((0 != msg->data.result) || (http_statuscode != 200))
1355         LOG (GNUNET_ERROR_TYPE_DEBUG,
1356              "Session %p/request %p: %s request to `%s' ended with status %i reason %i: `%s'\n",
1357              s, msg->easy_handle,
1358              (GNUNET_YES == put_request) ? "PUT" : "GET",
1359              GNUNET_i2s (&s->address->peer),
1360              http_statuscode,
1361              msg->data.result,
1362              curl_easy_strerror (msg->data.result));
1363       else
1364         LOG (GNUNET_ERROR_TYPE_DEBUG,
1365              "Session %p/request %p: %s request to `%s' ended normal\n",
1366              s, msg->easy_handle,
1367              (GNUNET_YES == put_request) ? "PUT" : "GET",
1368              GNUNET_i2s (&s->address->peer));
1369
1370       /* Remove easy handle from multi handle */
1371       curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1372
1373       /* Clean up easy handle */
1374       curl_easy_cleanup (easy_h);
1375
1376       /* Remove information */
1377       GNUNET_assert (plugin->cur_requests > 0);
1378       plugin->cur_requests--;
1379       LOG  (GNUNET_ERROR_TYPE_INFO,
1380           "%s request to %s done, number of requests decreased to %u\n",
1381           (GNUNET_YES == put_request) ? "PUT" : "GET",
1382           s->url,
1383           plugin->cur_requests);
1384
1385       if (GNUNET_YES == put_request)
1386       {
1387         /* Clean up a PUT request */
1388         s->put.easyhandle = NULL;
1389         s->put.s = NULL;
1390
1391         switch (s->put.state) {
1392           case H_NOT_CONNECTED:
1393           case H_DISCONNECTED:
1394           case H_TMP_DISCONNECTED:
1395             /* This must not happen */
1396             GNUNET_break (0);
1397             break;
1398           case H_TMP_RECONNECT_REQUIRED:
1399             /* Transport called send while disconnect in progess, reconnect */
1400             if (GNUNET_SYSERR == client_connect_put (s))
1401             {
1402               /* Reconnect failed, disconnect session */
1403               http_client_plugin_session_disconnect (plugin, s);
1404             }
1405             break;
1406           case H_TMP_DISCONNECTING:
1407             /* PUT gets temporarily disconnected */
1408             s->put.state = H_TMP_DISCONNECTED;
1409             break;
1410           case H_PAUSED:
1411           case H_CONNECTED:
1412             /* PUT gets permanently disconnected */
1413             s->put.state = H_DISCONNECTED;
1414             http_client_plugin_session_disconnect (plugin, s);
1415             break;
1416           default:
1417             GNUNET_break (0);
1418             break;
1419         }
1420       }
1421       else if (GNUNET_NO == put_request)
1422       {
1423         /* Clean up a GET request */
1424         s->get.easyhandle = NULL;
1425         s->get.s = NULL;
1426
1427         /* If we are emulating an XHR client we need to make another GET
1428          * request.
1429          */
1430         if (GNUNET_YES == plugin->emulate_xhr)
1431         {
1432           if (GNUNET_SYSERR == client_connect_get (s))
1433             http_client_plugin_session_disconnect (plugin, s);
1434         }
1435         else
1436         {
1437           /* GET request was terminated, so disconnect session */
1438           http_client_plugin_session_disconnect (plugin, s);
1439         }
1440       }
1441       else
1442         GNUNET_break (0); /* Must not happen */
1443
1444       GNUNET_STATISTICS_set (plugin->env->stats,
1445                              HTTP_STAT_STR_CONNECTIONS,
1446                              plugin->cur_requests,
1447                              GNUNET_NO);
1448     }
1449   }
1450   while (mret == CURLM_CALL_MULTI_PERFORM);
1451   client_schedule (plugin, GNUNET_NO);
1452 }
1453
1454
1455 #ifdef SO_TCPSTEALTH
1456 /**
1457  * Open TCP socket with TCP STEALTH enabled.
1458  */
1459 static curl_socket_t
1460 open_tcp_stealth_socket_cb (void *clientp,
1461                             curlsocktype purpose,
1462                             struct curl_sockaddr *address)
1463 {
1464   struct Session *s = clientp;
1465   int ret;
1466
1467   switch (purpose)
1468   {
1469   case CURLSOCKTYPE_IPCXN:
1470     ret = socket (address->family,
1471                   address->socktype,
1472                   address->protocol);
1473     if (-1 == ret)
1474       return CURL_SOCKET_BAD;
1475     if ( ( (SOCK_STREAM != address->socktype) ||
1476            ( (0 != address->protocol) &&
1477              (IPPROTO_TCP != address->protocol))) )
1478       return (curl_socket_t) ret;
1479     if ( (0 != setsockopt (ret,
1480                            IPPROTO_TCP,
1481                            SO_TCPSTEALTH,
1482                            &s->target,
1483                            sizeof (struct GNUNET_PeerIdentity))) )
1484     {
1485       (void) close (ret);
1486       return CURL_SOCKET_BAD;
1487     }
1488     return (curl_socket_t) ret;
1489   case CURLSOCKTYPE_ACCEPT:
1490     GNUNET_break (0);
1491     return CURL_SOCKET_BAD;
1492     break;
1493   case CURLSOCKTYPE_LAST:
1494     GNUNET_break (0);
1495     return CURL_SOCKET_BAD;
1496   }
1497 }
1498 #endif
1499
1500
1501 /**
1502  * Connect GET request for a session
1503  *
1504  * @param s the session to connect
1505  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1506  */
1507 static int
1508 client_connect_get (struct Session *s)
1509 {
1510   CURLMcode mret;
1511   struct HttpAddress *ha;
1512   uint32_t options;
1513
1514   ha = (struct HttpAddress *) s->address->address;
1515   options = ntohl (ha->options);
1516   /* create get request */
1517   s->get.easyhandle = curl_easy_init ();
1518   s->get.s = s;
1519   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1520   {
1521 #ifdef SO_TCPSTEALTH
1522     curl_easy_setopt (s->get.easyhandle,
1523                       CURLOPT_OPENSOCKETFUNCTION,
1524                       &open_tcp_stealth_socket_cb);
1525     curl_easy_setopt (s->get.easyhandle,
1526                       CURLOPT_OPENSOCKETDATA,
1527                       s);
1528 #else
1529     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1530                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1531     curl_easy_cleanup (s->get.easyhandle);
1532     s->get.easyhandle = NULL;
1533     s->get.s = NULL;
1534     return GNUNET_SYSERR;
1535 #endif
1536   }
1537
1538 #if VERBOSE_CURL
1539   curl_easy_setopt (s->get.easyhandle,
1540                     CURLOPT_VERBOSE,
1541                     1L);
1542   curl_easy_setopt (s->get.easyhandle,
1543                     CURLOPT_DEBUGFUNCTION,
1544                     &client_log);
1545   curl_easy_setopt (s->get.easyhandle,
1546                     CURLOPT_DEBUGDATA,
1547                     &s->get);
1548 #endif
1549 #if BUILD_HTTPS
1550   curl_easy_setopt (s->get.easyhandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1551   {
1552     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1553         (options & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1554     {
1555       curl_easy_setopt (s->get.easyhandle,
1556                         CURLOPT_SSL_VERIFYPEER, 1L);
1557       curl_easy_setopt (s->get.easyhandle,
1558                         CURLOPT_SSL_VERIFYHOST,
1559                         2L);
1560     }
1561     else
1562     {
1563       curl_easy_setopt (s->get.easyhandle,
1564                         CURLOPT_SSL_VERIFYPEER,
1565                         0L);
1566       curl_easy_setopt (s->get.easyhandle,
1567                         CURLOPT_SSL_VERIFYHOST,
1568                         0L);
1569     }
1570   }
1571   curl_easy_setopt (s->get.easyhandle,
1572                     CURLOPT_PROTOCOLS,
1573                     CURLPROTO_HTTPS);
1574   curl_easy_setopt (s->get.easyhandle,
1575                     CURLOPT_REDIR_PROTOCOLS,
1576                     CURLPROTO_HTTPS);
1577 #else
1578   curl_easy_setopt (s->get.easyhandle,
1579                     CURLOPT_PROTOCOLS,
1580                     CURLPROTO_HTTP);
1581   curl_easy_setopt (s->get.easyhandle,
1582                     CURLOPT_REDIR_PROTOCOLS,
1583                     CURLPROTO_HTTP);
1584 #endif
1585
1586   if (NULL != s->plugin->proxy_hostname)
1587   {
1588     curl_easy_setopt (s->get.easyhandle,
1589                       CURLOPT_PROXY,
1590                       s->plugin->proxy_hostname);
1591     curl_easy_setopt (s->get.easyhandle,
1592                       CURLOPT_PROXYTYPE,
1593                       s->plugin->proxytype);
1594     if (NULL != s->plugin->proxy_username)
1595       curl_easy_setopt (s->get.easyhandle,
1596                         CURLOPT_PROXYUSERNAME,
1597                         s->plugin->proxy_username);
1598     if (NULL != s->plugin->proxy_password)
1599       curl_easy_setopt (s->get.easyhandle,
1600                         CURLOPT_PROXYPASSWORD,
1601                         s->plugin->proxy_password);
1602     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1603       curl_easy_setopt (s->get.easyhandle,
1604                         CURLOPT_HTTPPROXYTUNNEL,
1605                         s->plugin->proxy_use_httpproxytunnel);
1606   }
1607
1608   if (GNUNET_YES == s->plugin->emulate_xhr)
1609   {
1610     char *url;
1611
1612     GNUNET_asprintf (&url,
1613                      "%s,1",
1614                      s->url);
1615     curl_easy_setopt (s->get.easyhandle,
1616                       CURLOPT_URL,
1617                       url);
1618     GNUNET_free(url);
1619   }
1620   else
1621   {
1622     curl_easy_setopt (s->get.easyhandle,
1623                       CURLOPT_URL,
1624                       s->url);
1625   }
1626   curl_easy_setopt (s->get.easyhandle,
1627                     CURLOPT_READFUNCTION,
1628                     &client_send_cb);
1629   curl_easy_setopt (s->get.easyhandle,
1630                     CURLOPT_READDATA,
1631                     s);
1632   curl_easy_setopt (s->get.easyhandle,
1633                     CURLOPT_WRITEFUNCTION,
1634                     &client_receive);
1635   curl_easy_setopt (s->get.easyhandle,
1636                     CURLOPT_WRITEDATA,
1637                     s);
1638   /* No timeout by default, timeout done with session timeout */
1639   curl_easy_setopt (s->get.easyhandle,
1640                     CURLOPT_TIMEOUT,
1641                     0L);
1642   curl_easy_setopt (s->get.easyhandle,
1643                     CURLOPT_PRIVATE, s);
1644   curl_easy_setopt (s->get.easyhandle,
1645                     CURLOPT_CONNECTTIMEOUT_MS,
1646                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1647   curl_easy_setopt (s->get.easyhandle, CURLOPT_BUFFERSIZE,
1648                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1649 #if CURL_TCP_NODELAY
1650   curl_easy_setopt (ps->recv_endpoint,
1651                     CURLOPT_TCP_NODELAY,
1652                     1L);
1653 #endif
1654   curl_easy_setopt (s->get.easyhandle,
1655                     CURLOPT_FOLLOWLOCATION,
1656                     0L);
1657
1658   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1659                                 s->get.easyhandle);
1660   if (CURLM_OK != mret)
1661   {
1662     LOG (GNUNET_ERROR_TYPE_ERROR,
1663          "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1664          s,
1665          curl_multi_strerror (mret));
1666     curl_easy_cleanup (s->get.easyhandle);
1667     s->get.easyhandle = NULL;
1668     s->get.s = NULL;
1669     GNUNET_break (0);
1670     return GNUNET_SYSERR;
1671   }
1672   s->plugin->cur_requests++;
1673   LOG (GNUNET_ERROR_TYPE_INFO,
1674        "GET request `%s' established, number of requests increased to %u\n",
1675        s->url,
1676        s->plugin->cur_requests);
1677   return GNUNET_OK;
1678 }
1679
1680
1681 /**
1682  * Connect a HTTP put request
1683  *
1684  * @param s the session to connect
1685  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1686  */
1687 static int
1688 client_connect_put (struct Session *s)
1689 {
1690   CURLMcode mret;
1691   struct HttpAddress *ha;
1692   uint32_t options;
1693
1694   ha = (struct HttpAddress *) s->address->address;
1695   options = ntohl (ha->options);
1696   /* create put request */
1697   LOG (GNUNET_ERROR_TYPE_DEBUG,
1698        "Session %p: Init PUT handle\n",
1699        s);
1700   s->put.easyhandle = curl_easy_init ();
1701   s->put.s = s;
1702 #if VERBOSE_CURL
1703   curl_easy_setopt (s->put.easyhandle,
1704                     CURLOPT_VERBOSE,
1705                     1L);
1706   curl_easy_setopt (s->put.easyhandle,
1707                     CURLOPT_DEBUGFUNCTION,
1708                     &client_log);
1709   curl_easy_setopt (s->put.easyhandle,
1710                     CURLOPT_DEBUGDATA,
1711                     &s->put);
1712 #endif
1713   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1714   {
1715 #ifdef SO_TCPSTEALTH
1716     curl_easy_setopt (s->put.easyhandle,
1717                       CURLOPT_OPENSOCKETFUNCTION,
1718                       &open_tcp_stealth_socket_cb);
1719     curl_easy_setopt (s->put.easyhandle,
1720                       CURLOPT_OPENSOCKETDATA,
1721                       s);
1722 #else
1723     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1724                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1725     curl_easy_cleanup (s->put.easyhandle);
1726     s->put.easyhandle = NULL;
1727     s->put.s = NULL;
1728     s->put.state = H_DISCONNECTED;
1729     return GNUNET_SYSERR;
1730 #endif
1731   }
1732 #if BUILD_HTTPS
1733   curl_easy_setopt (s->put.easyhandle,
1734                     CURLOPT_SSLVERSION,
1735                     CURL_SSLVERSION_TLSv1);
1736   {
1737     struct HttpAddress *ha;
1738     ha = (struct HttpAddress *) s->address->address;
1739
1740     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1741         (ntohl (ha->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1742     {
1743       curl_easy_setopt (s->put.easyhandle,
1744                         CURLOPT_SSL_VERIFYPEER,
1745                         1L);
1746       curl_easy_setopt (s->put.easyhandle,
1747                         CURLOPT_SSL_VERIFYHOST,
1748                         2L);
1749     }
1750     else
1751     {
1752       curl_easy_setopt (s->put.easyhandle,
1753                         CURLOPT_SSL_VERIFYPEER,
1754                         0L);
1755       curl_easy_setopt (s->put.easyhandle,
1756                         CURLOPT_SSL_VERIFYHOST,
1757                         0L);
1758     }
1759   }
1760   curl_easy_setopt (s->put.easyhandle,
1761                     CURLOPT_PROTOCOLS,
1762                     CURLPROTO_HTTPS);
1763   curl_easy_setopt (s->put.easyhandle,
1764                     CURLOPT_REDIR_PROTOCOLS,
1765                     CURLPROTO_HTTPS);
1766 #else
1767   curl_easy_setopt (s->put.easyhandle,
1768                     CURLOPT_PROTOCOLS,
1769                     CURLPROTO_HTTP);
1770   curl_easy_setopt (s->put.easyhandle,
1771                     CURLOPT_REDIR_PROTOCOLS,
1772                     CURLPROTO_HTTP);
1773 #endif
1774   if (NULL != s->plugin->proxy_hostname)
1775   {
1776     curl_easy_setopt (s->put.easyhandle,
1777                       CURLOPT_PROXY,
1778                       s->plugin->proxy_hostname);
1779     curl_easy_setopt (s->put.easyhandle,
1780                       CURLOPT_PROXYTYPE,
1781                       s->plugin->proxytype);
1782     if (NULL != s->plugin->proxy_username)
1783       curl_easy_setopt (s->put.easyhandle,
1784                         CURLOPT_PROXYUSERNAME,
1785                         s->plugin->proxy_username);
1786     if (NULL != s->plugin->proxy_password)
1787       curl_easy_setopt (s->put.easyhandle,
1788                         CURLOPT_PROXYPASSWORD,
1789                         s->plugin->proxy_password);
1790     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1791       curl_easy_setopt (s->put.easyhandle,
1792                         CURLOPT_HTTPPROXYTUNNEL,
1793                         s->plugin->proxy_use_httpproxytunnel);
1794   }
1795
1796   curl_easy_setopt (s->put.easyhandle,
1797                     CURLOPT_URL,
1798                     s->url);
1799   curl_easy_setopt (s->put.easyhandle,
1800                     CURLOPT_UPLOAD,
1801                     1L);
1802   curl_easy_setopt (s->put.easyhandle,
1803                     CURLOPT_READFUNCTION,
1804                     &client_send_cb);
1805   curl_easy_setopt (s->put.easyhandle,
1806                     CURLOPT_READDATA,
1807                     s);
1808   curl_easy_setopt (s->put.easyhandle,
1809                     CURLOPT_WRITEFUNCTION,
1810                     &client_receive_put);
1811   curl_easy_setopt (s->put.easyhandle,
1812                     CURLOPT_WRITEDATA,
1813                     s);
1814   /* No timeout by default, timeout done with session timeout */
1815   curl_easy_setopt (s->put.easyhandle,
1816                     CURLOPT_TIMEOUT,
1817                     0L);
1818   curl_easy_setopt (s->put.easyhandle,
1819                     CURLOPT_PRIVATE,
1820                     s);
1821   curl_easy_setopt (s->put.easyhandle,
1822                     CURLOPT_CONNECTTIMEOUT_MS,
1823                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1824   curl_easy_setopt (s->put.easyhandle, CURLOPT_BUFFERSIZE,
1825                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1826 #if CURL_TCP_NODELAY
1827   curl_easy_setopt (s->put.easyhandle, CURLOPT_TCP_NODELAY, 1);
1828 #endif
1829   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1830                                 s->put.easyhandle);
1831   if (CURLM_OK != mret)
1832   {
1833     LOG (GNUNET_ERROR_TYPE_ERROR,
1834          "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1835          s, curl_multi_strerror (mret));
1836     curl_easy_cleanup (s->put.easyhandle);
1837     s->put.easyhandle = NULL;
1838     s->put.s = NULL;
1839     s->put.state = H_DISCONNECTED;
1840     return GNUNET_SYSERR;
1841   }
1842   s->put.state = H_CONNECTED;
1843   s->plugin->cur_requests++;
1844
1845   LOG  (GNUNET_ERROR_TYPE_INFO,
1846       "PUT request `%s' established, number of requests increased to %u\n",
1847       s->url, s->plugin->cur_requests);
1848
1849   return GNUNET_OK;
1850 }
1851
1852
1853 /**
1854  * Connect both PUT and GET request for a session
1855  *
1856  * @param s the session to connect
1857  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1858  */
1859 static int
1860 client_connect (struct Session *s)
1861 {
1862   struct HTTP_Client_Plugin *plugin = s->plugin;
1863   int res = GNUNET_OK;
1864
1865   /* create url */
1866   if (NULL ==
1867       http_common_plugin_address_to_string(plugin->protocol,
1868                                            s->address->address,
1869                                            s->address->address_length))
1870   {
1871     LOG (GNUNET_ERROR_TYPE_DEBUG,
1872          "Invalid address peer `%s'\n",
1873          GNUNET_i2s(&s->address->peer));
1874     return GNUNET_SYSERR;
1875   }
1876
1877   GNUNET_asprintf(&s->url,
1878                   "%s/%s;%u",
1879                   http_common_plugin_address_to_url(NULL,
1880                                                     s->address->address,
1881                                                     s->address->address_length),
1882                   GNUNET_i2s_full (plugin->env->my_identity),
1883                   plugin->last_tag);
1884
1885   plugin->last_tag++;
1886   LOG (GNUNET_ERROR_TYPE_DEBUG,
1887        "Initiating outbound session peer `%s' using address `%s'\n",
1888        GNUNET_i2s (&s->address->peer), s->url);
1889
1890   if (GNUNET_SYSERR == client_connect_get (s))
1891     return GNUNET_SYSERR;
1892   /* If we are emulating an XHR client then delay sending a PUT request until
1893    * there is something to send.
1894    */
1895   if (GNUNET_YES == plugin->emulate_xhr)
1896   {
1897     s->put.state = H_TMP_DISCONNECTED;
1898   }
1899   else if (GNUNET_SYSERR == client_connect_put (s))
1900     return GNUNET_SYSERR;
1901
1902   LOG (GNUNET_ERROR_TYPE_DEBUG,
1903        "Session %p: connected with GET %p and PUT %p\n",
1904        s, s->get.easyhandle, s->put.easyhandle);
1905   /* Perform connect */
1906   GNUNET_STATISTICS_set (plugin->env->stats,
1907                          HTTP_STAT_STR_CONNECTIONS,
1908                          plugin->cur_requests,
1909                          GNUNET_NO);
1910   /* Re-schedule since handles have changed */
1911   if (plugin->client_perform_task != GNUNET_SCHEDULER_NO_TASK)
1912   {
1913     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1914     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
1915   }
1916
1917   /* Schedule task to run immediately */
1918   plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run, plugin);
1919   return res;
1920 }
1921
1922
1923 /**
1924  * Function obtain the network type for a session
1925  *
1926  * @param cls closure (`struct Plugin*`)
1927  * @param session the session
1928  * @return the network type
1929  */
1930 static enum GNUNET_ATS_Network_Type
1931 http_client_plugin_get_network (void *cls,
1932                                 struct Session *session)
1933 {
1934   return ntohl (session->ats_address_network_type);
1935 }
1936
1937
1938 /**
1939  * Session was idle, so disconnect it
1940  *
1941  * @param cls the `struct Session` of the idle session
1942  * @param tc scheduler context
1943  */
1944 static void
1945 client_session_timeout (void *cls,
1946                         const struct GNUNET_SCHEDULER_TaskContext *tc)
1947 {
1948   struct Session *s = cls;
1949   struct GNUNET_TIME_Relative left;
1950
1951   s->timeout_task = GNUNET_SCHEDULER_NO_TASK;
1952   left = GNUNET_TIME_absolute_get_remaining (s->timeout);
1953   if (0 != left.rel_value_us)
1954   {
1955     /* not actually our turn yet, but let's at least update
1956        the monitor, it may think we're about to die ... */
1957     notify_session_monitor (s->plugin,
1958                             s,
1959                             GNUNET_TRANSPORT_SS_UPDATE);
1960     s->timeout_task = GNUNET_SCHEDULER_add_delayed (left,
1961                                                     &client_session_timeout,
1962                                                     s);
1963     return;
1964   }
1965   LOG (TIMEOUT_LOG,
1966        "Session %p was idle for %s, disconnecting\n",
1967        s,
1968        GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1969                                                GNUNET_YES));
1970   GNUNET_assert (GNUNET_OK ==
1971                  http_client_plugin_session_disconnect (s->plugin,
1972                                                  s));
1973 }
1974
1975
1976 /**
1977  * Creates a new outbound session the transport service will use to
1978  * send data to the peer
1979  *
1980  * @param cls the plugin
1981  * @param address the address
1982  * @return the session or NULL of max connections exceeded
1983  */
1984 static struct Session *
1985 http_client_plugin_get_session (void *cls,
1986                                 const struct GNUNET_HELLO_Address *address)
1987 {
1988   struct HTTP_Client_Plugin *plugin = cls;
1989   struct Session *s;
1990   struct sockaddr *sa;
1991   struct GNUNET_ATS_Information ats;
1992   size_t salen = 0;
1993   int res;
1994
1995   GNUNET_assert (NULL != address->address);
1996
1997   /* find existing session */
1998   s = client_lookup_session (plugin, address);
1999   if (NULL != s)
2000     return s;
2001
2002   /* create a new session */
2003   if (plugin->max_requests <= plugin->cur_requests)
2004   {
2005     LOG (GNUNET_ERROR_TYPE_WARNING,
2006          "Maximum number of requests (%u) reached: "
2007          "cannot connect to peer `%s'\n",
2008          plugin->max_requests,
2009          GNUNET_i2s (&address->peer));
2010     return NULL;
2011   }
2012
2013   /* Determine network location */
2014   ats.type = htonl (GNUNET_ATS_NETWORK_TYPE);
2015   ats.value = htonl (GNUNET_ATS_NET_UNSPECIFIED);
2016   sa = http_common_socket_from_address (address->address,
2017                                         address->address_length,
2018                                         &res);
2019   if (GNUNET_SYSERR == res)
2020     return NULL;
2021   if (GNUNET_YES == res)
2022   {
2023     GNUNET_assert (NULL != sa);
2024     if (AF_INET == sa->sa_family)
2025     {
2026       salen = sizeof (struct sockaddr_in);
2027     }
2028     else if (AF_INET6 == sa->sa_family)
2029     {
2030       salen = sizeof (struct sockaddr_in6);
2031     }
2032     ats = plugin->env->get_address_type (plugin->env->cls, sa, salen);
2033     GNUNET_free (sa);
2034   }
2035   else if (GNUNET_NO == res)
2036   {
2037     /* Cannot convert to sockaddr -> is external hostname */
2038     ats.value = htonl (GNUNET_ATS_NET_WAN);
2039   }
2040   if (GNUNET_ATS_NET_UNSPECIFIED == ntohl (ats.value))
2041   {
2042     GNUNET_break (0);
2043     return NULL;
2044   }
2045
2046   s = GNUNET_new (struct Session);
2047   s->plugin = plugin;
2048   s->address = GNUNET_HELLO_address_copy (address);
2049   s->ats_address_network_type = ats.value;
2050
2051   s->put.state = H_NOT_CONNECTED;
2052   s->timeout = GNUNET_TIME_relative_to_absolute (HTTP_CLIENT_SESSION_TIMEOUT);
2053   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
2054                                                    &client_session_timeout,
2055                                                    s);
2056   LOG (GNUNET_ERROR_TYPE_DEBUG,
2057        "Created new session %p for `%s' address `%s''\n",
2058        s,
2059        http_common_plugin_address_to_string (plugin->protocol,
2060                                              s->address->address,
2061                                              s->address->address_length),
2062        GNUNET_i2s (&s->address->peer));
2063
2064   /* add new session */
2065   (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessions,
2066                                             &s->address->peer,
2067                                             s,
2068                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
2069   /* initiate new connection */
2070   if (GNUNET_SYSERR == client_connect (s))
2071   {
2072     LOG (GNUNET_ERROR_TYPE_ERROR,
2073          "Cannot connect to peer `%s' address `%s''\n",
2074          http_common_plugin_address_to_string (plugin->protocol,
2075              s->address->address, s->address->address_length),
2076              GNUNET_i2s (&s->address->peer));
2077     client_delete_session (s);
2078     return NULL;
2079   }
2080   notify_session_monitor (plugin,
2081                           s,
2082                           GNUNET_TRANSPORT_SS_INIT);
2083   notify_session_monitor (plugin,
2084                           s,
2085                           GNUNET_TRANSPORT_SS_UP); /* or handshake? */
2086   return s;
2087 }
2088
2089
2090 /**
2091  * Setup http_client plugin
2092  *
2093  * @param plugin the plugin handle
2094  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
2095  */
2096 static int
2097 client_start (struct HTTP_Client_Plugin *plugin)
2098 {
2099   curl_global_init (CURL_GLOBAL_ALL);
2100   plugin->curl_multi_handle = curl_multi_init ();
2101
2102   if (NULL == plugin->curl_multi_handle)
2103   {
2104     LOG (GNUNET_ERROR_TYPE_ERROR,
2105          _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
2106          plugin->name);
2107     return GNUNET_SYSERR;
2108   }
2109   return GNUNET_OK;
2110 }
2111
2112
2113 /**
2114  * Another peer has suggested an address for this
2115  * peer and transport plugin.  Check that this could be a valid
2116  * address.  If so, consider adding it to the list
2117  * of addresses.
2118  *
2119  * @param cls closure with the `struct Plugin`
2120  * @param addr pointer to the address
2121  * @param addrlen length of @a addr
2122  * @return #GNUNET_OK if this is a plausible address for this peer
2123  *         and transport; always returns #GNUNET_NO (this is the client!)
2124  */
2125 static int
2126 http_client_plugin_address_suggested (void *cls,
2127                                       const void *addr,
2128                                       size_t addrlen)
2129 {
2130   /* A HTTP/S client does not have any valid address so:*/
2131   return GNUNET_NO;
2132 }
2133
2134
2135 /**
2136  * Exit point from the plugin.
2137  *
2138  * @param cls api as closure
2139  * @return NULL
2140  */
2141 void *
2142 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
2143 {
2144   struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
2145   struct HTTP_Client_Plugin *plugin = api->cls;
2146
2147   if (NULL == api->cls)
2148   {
2149     /* Stub shutdown */
2150     GNUNET_free (api);
2151     return NULL;
2152   }
2153   LOG (GNUNET_ERROR_TYPE_DEBUG,
2154        _("Shutting down plugin `%s'\n"),
2155        plugin->name);
2156   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2157                                          &destroy_session_cb,
2158                                          plugin);
2159   if (GNUNET_SCHEDULER_NO_TASK != plugin->client_perform_task)
2160   {
2161     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
2162     plugin->client_perform_task = GNUNET_SCHEDULER_NO_TASK;
2163   }
2164   if (NULL != plugin->curl_multi_handle)
2165   {
2166     curl_multi_cleanup (plugin->curl_multi_handle);
2167     plugin->curl_multi_handle = NULL;
2168   }
2169   curl_global_cleanup ();
2170   LOG (GNUNET_ERROR_TYPE_DEBUG,
2171        _("Shutdown for plugin `%s' complete\n"),
2172        plugin->name);
2173   GNUNET_CONTAINER_multipeermap_destroy (plugin->sessions);
2174   GNUNET_free_non_null (plugin->proxy_hostname);
2175   GNUNET_free_non_null (plugin->proxy_username);
2176   GNUNET_free_non_null (plugin->proxy_password);
2177   GNUNET_free (plugin);
2178   GNUNET_free (api);
2179   return NULL;
2180 }
2181
2182
2183 /**
2184  * Configure plugin
2185  *
2186  * @param plugin the plugin handle
2187  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
2188  */
2189 static int
2190 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
2191 {
2192   unsigned long long max_requests;
2193   char *proxy_type;
2194
2195   /* Optional parameters */
2196   if (GNUNET_OK !=
2197       GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
2198                                              plugin->name,
2199                                              "MAX_CONNECTIONS",
2200                                              &max_requests))
2201     max_requests = 128;
2202   plugin->max_requests = max_requests;
2203
2204   LOG (GNUNET_ERROR_TYPE_DEBUG,
2205        _("Maximum number of requests is %u\n"),
2206        plugin->max_requests);
2207
2208   /* Read proxy configuration */
2209   if (GNUNET_OK ==
2210       GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2211                                              plugin->name,
2212                                              "PROXY",
2213                                              &plugin->proxy_hostname))
2214   {
2215     LOG (GNUNET_ERROR_TYPE_DEBUG,
2216          "Found proxy host: `%s'\n",
2217          plugin->proxy_hostname);
2218     /* proxy username */
2219     if (GNUNET_OK ==
2220         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2221                                                plugin->name,
2222                                                "PROXY_USERNAME",
2223                                                &plugin->proxy_username))
2224     {
2225       LOG (GNUNET_ERROR_TYPE_DEBUG,
2226            "Found proxy username name: `%s'\n",
2227            plugin->proxy_username);
2228     }
2229
2230     /* proxy password */
2231     if (GNUNET_OK ==
2232         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2233                                                plugin->name,
2234                                                "PROXY_PASSWORD",
2235                                                &plugin->proxy_password))
2236     {
2237       LOG (GNUNET_ERROR_TYPE_DEBUG,
2238            "Found proxy password name: `%s'\n",
2239            plugin->proxy_password);
2240     }
2241
2242     /* proxy type */
2243     if (GNUNET_OK ==
2244         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2245                                                plugin->name,
2246                                                "PROXY_TYPE",
2247                                                &proxy_type))
2248     {
2249       GNUNET_STRINGS_utf8_toupper (proxy_type, proxy_type);
2250
2251       if (0 == strcmp(proxy_type, "HTTP"))
2252         plugin->proxytype = CURLPROXY_HTTP;
2253       else if (0 == strcmp(proxy_type, "SOCKS4"))
2254         plugin->proxytype = CURLPROXY_SOCKS4;
2255       else if (0 == strcmp(proxy_type, "SOCKS5"))
2256         plugin->proxytype = CURLPROXY_SOCKS5;
2257       else if (0 == strcmp(proxy_type, "SOCKS4A"))
2258         plugin->proxytype = CURLPROXY_SOCKS4A;
2259       else if (0 == strcmp(proxy_type, "SOCKS5_HOSTNAME "))
2260         plugin->proxytype = CURLPROXY_SOCKS5_HOSTNAME ;
2261       else
2262       {
2263         LOG (GNUNET_ERROR_TYPE_ERROR,
2264              _("Invalid proxy type: `%s', disabling proxy! Check configuration!\n"),
2265              proxy_type);
2266
2267         GNUNET_free (proxy_type);
2268         GNUNET_free (plugin->proxy_hostname);
2269         plugin->proxy_hostname = NULL;
2270         GNUNET_free_non_null (plugin->proxy_username);
2271         plugin->proxy_username = NULL;
2272         GNUNET_free_non_null (plugin->proxy_password);
2273         plugin->proxy_password = NULL;
2274
2275         return GNUNET_SYSERR;
2276       }
2277
2278       LOG (GNUNET_ERROR_TYPE_DEBUG,
2279            "Found proxy type: `%s'\n",
2280            proxy_type);
2281     }
2282
2283     /* proxy http tunneling */
2284     plugin->proxy_use_httpproxytunnel
2285       = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2286                                               plugin->name,
2287                                               "PROXY_HTTP_TUNNELING");
2288     if (GNUNET_SYSERR == plugin->proxy_use_httpproxytunnel)
2289       plugin->proxy_use_httpproxytunnel = GNUNET_NO;
2290
2291     GNUNET_free_non_null (proxy_type);
2292   }
2293
2294   /* Should we emulate an XHR client for testing? */
2295   plugin->emulate_xhr
2296     = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2297                                             plugin->name,
2298                                             "EMULATE_XHR");
2299   return GNUNET_OK;
2300 }
2301
2302
2303 /**
2304  * Function to convert an address to a human-readable string.
2305  *
2306  * @param cls closure
2307  * @param addr address to convert
2308  * @param addrlen address length
2309  * @return res string if conversion was successful, NULL otherwise
2310  */
2311 static const char *
2312 http_client_plugin_address_to_string (void *cls,
2313                                       const void *addr,
2314                                       size_t addrlen)
2315 {
2316   return http_common_plugin_address_to_string (PLUGIN_NAME,
2317                                                addr,
2318                                                addrlen);
2319 }
2320
2321
2322 /**
2323  * Function that will be called whenever the transport service wants to
2324  * notify the plugin that a session is still active and in use and
2325  * therefore the session timeout for this session has to be updated
2326  *
2327  * @param cls closure
2328  * @param peer which peer was the session for
2329  * @param session which session is being updated
2330  */
2331 static void
2332 http_client_plugin_update_session_timeout (void *cls,
2333                                            const struct GNUNET_PeerIdentity *peer,
2334                                            struct Session *session)
2335 {
2336   client_reschedule_session_timeout (session);
2337 }
2338
2339
2340 /**
2341  * Function that will be called whenever the transport service wants to
2342  * notify the plugin that the inbound quota changed and that the plugin
2343  * should update it's delay for the next receive value
2344  *
2345  * @param cls closure
2346  * @param peer which peer was the session for
2347  * @param s which session is being updated
2348  * @param delay new delay to use for receiving
2349  */
2350 static void
2351 http_client_plugin_update_inbound_delay (void *cls,
2352                                          const struct GNUNET_PeerIdentity *peer,
2353                                          struct Session *s,
2354                                          struct GNUNET_TIME_Relative delay)
2355 {
2356   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
2357   LOG (GNUNET_ERROR_TYPE_DEBUG,
2358        "New inbound delay %s\n",
2359        GNUNET_STRINGS_relative_time_to_string (delay,
2360                                                GNUNET_NO));
2361   if (s->recv_wakeup_task != GNUNET_SCHEDULER_NO_TASK)
2362   {
2363     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
2364     s->recv_wakeup_task = GNUNET_SCHEDULER_add_delayed (delay,
2365         &client_wake_up, s);
2366   }
2367 }
2368
2369
2370 /**
2371  * Return information about the given session to the
2372  * monitor callback.
2373  *
2374  * @param cls the `struct Plugin` with the monitor callback (`sic`)
2375  * @param peer peer we send information about
2376  * @param value our `struct Session` to send information about
2377  * @return #GNUNET_OK (continue to iterate)
2378  */
2379 static int
2380 send_session_info_iter (void *cls,
2381                         const struct GNUNET_PeerIdentity *peer,
2382                         void *value)
2383 {
2384   struct HTTP_Client_Plugin *plugin = cls;
2385   struct Session *session = value;
2386
2387   notify_session_monitor (plugin,
2388                           session,
2389                           GNUNET_TRANSPORT_SS_INIT);
2390   notify_session_monitor (plugin,
2391                           session,
2392                           GNUNET_TRANSPORT_SS_UP); /* FIXME: or handshake? */
2393   return GNUNET_OK;
2394 }
2395
2396
2397 /**
2398  * Begin monitoring sessions of a plugin.  There can only
2399  * be one active monitor per plugin (i.e. if there are
2400  * multiple monitors, the transport service needs to
2401  * multiplex the generated events over all of them).
2402  *
2403  * @param cls closure of the plugin
2404  * @param sic callback to invoke, NULL to disable monitor;
2405  *            plugin will being by iterating over all active
2406  *            sessions immediately and then enter monitor mode
2407  * @param sic_cls closure for @a sic
2408  */
2409 static void
2410 http_client_plugin_setup_monitor (void *cls,
2411                                   GNUNET_TRANSPORT_SessionInfoCallback sic,
2412                                   void *sic_cls)
2413 {
2414   struct HTTP_Client_Plugin *plugin = cls;
2415
2416   plugin->sic = sic;
2417   plugin->sic_cls = sic_cls;
2418   if (NULL != sic)
2419   {
2420     GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2421                                            &send_session_info_iter,
2422                                            plugin);
2423     /* signal end of first iteration */
2424     sic (sic_cls, NULL, NULL);
2425   }
2426 }
2427
2428
2429 /**
2430  * Entry point for the plugin.
2431  */
2432 void *
2433 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
2434 {
2435   struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
2436   struct GNUNET_TRANSPORT_PluginFunctions *api;
2437   struct HTTP_Client_Plugin *plugin;
2438
2439   if (NULL == env->receive)
2440   {
2441     /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
2442        initialze the plugin or the API */
2443     api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2444     api->cls = NULL;
2445     api->address_to_string = &http_client_plugin_address_to_string;
2446     api->string_to_address = &http_common_plugin_string_to_address;
2447     api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2448     return api;
2449   }
2450
2451   plugin = GNUNET_new (struct HTTP_Client_Plugin);
2452   plugin->env = env;
2453   plugin->sessions = GNUNET_CONTAINER_multipeermap_create (128,
2454                                                            GNUNET_YES);
2455   api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2456   api->cls = plugin;
2457   api->send = &http_client_plugin_send;
2458   api->disconnect_session = &http_client_plugin_session_disconnect;
2459   api->query_keepalive_factor = &http_client_query_keepalive_factor;
2460   api->disconnect_peer = &http_client_plugin_peer_disconnect;
2461   api->check_address = &http_client_plugin_address_suggested;
2462   api->get_session = &http_client_plugin_get_session;
2463   api->address_to_string = &http_client_plugin_address_to_string;
2464   api->string_to_address = &http_common_plugin_string_to_address;
2465   api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2466   api->get_network = &http_client_plugin_get_network;
2467   api->update_session_timeout = &http_client_plugin_update_session_timeout;
2468   api->update_inbound_delay = &http_client_plugin_update_inbound_delay;
2469   api->setup_monitor = &http_client_plugin_setup_monitor;
2470 #if BUILD_HTTPS
2471   plugin->name = "transport-https_client";
2472   plugin->protocol = "https";
2473 #else
2474   plugin->name = "transport-http_client";
2475   plugin->protocol = "http";
2476 #endif
2477   plugin->last_tag = 1;
2478
2479   if (GNUNET_SYSERR == client_configure_plugin (plugin))
2480   {
2481     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2482     return NULL;
2483   }
2484
2485   /* Start client */
2486   if (GNUNET_SYSERR == client_start (plugin))
2487   {
2488     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2489     return NULL;
2490   }
2491   return api;
2492 }
2493
2494 /* end of plugin_transport_http_client.c */