Fix #5514; Add test for GNS Proxy and DANE; Fix TLS connections on ports != 443 throu...
[oweals/gnunet.git] / src / gns / test_gns_proxy.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2007, 2009, 2011, 2012 Christian Grothoff
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21 /**
22  * @file test_gns_proxy.c
23  * @brief testcase for accessing SOCKS5 GNS proxy
24  * @author Martin Schanzenbach
25  */
26 #include "platform.h"
27 #if HAVE_CURL_CURL_H
28 #include <curl/curl.h>
29 #elif HAVE_GNURL_CURL_H
30 #include <gnurl/curl.h>
31 #endif
32 #include <microhttpd.h>
33 #include "gnunet_util_lib.h"
34 #include "gnutls/x509.h"
35
36 /**
37  * Largest allowed size for a PEM certificate.
38  */
39 #define MAX_PEM_SIZE (10 * 1024)
40
41 #define TEST_DOMAIN "www.test"
42
43 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 300)
44
45 /**
46  * Return value for 'main'.
47  */
48 static int global_ret;
49
50
51 static struct MHD_Daemon *mhd;
52
53 static struct GNUNET_SCHEDULER_Task * mhd_task_id;
54
55 static struct GNUNET_SCHEDULER_Task * curl_task_id;
56
57 static CURL *curl;
58
59 static CURLM *multi;
60
61 static char *url;
62
63 static struct GNUNET_OS_Process *proxy_proc;
64
65 static char* cafile_opt;
66
67 static char* cafile_srv;
68
69 static uint16_t port;
70
71 static gnutls_x509_crt_t proxy_cert;
72
73 static gnutls_x509_privkey_t proxy_key;
74
75 struct CBC
76 {
77   char buf[1024];
78   size_t pos;
79 };
80
81 static struct CBC cbc;
82
83 /**
84  * Read file in filename
85  *
86  * @param filename file to read
87  * @param size pointer where filesize is stored
88  * @return NULL on error
89  */
90 static void*
91 load_file (const char* filename,
92            unsigned int* size)
93 {
94   void *buffer;
95   uint64_t fsize;
96
97   if (GNUNET_OK !=
98       GNUNET_DISK_file_size (filename,
99                              &fsize,
100                              GNUNET_YES,
101                              GNUNET_YES))
102     return NULL;
103   if (fsize > MAX_PEM_SIZE)
104     return NULL;
105   *size = (unsigned int) fsize;
106   buffer = GNUNET_malloc (*size);
107   if (fsize !=
108       GNUNET_DISK_fn_read (filename,
109                            buffer,
110                            (size_t) fsize))
111   {
112     GNUNET_free (buffer);
113     return NULL;
114   }
115   return buffer;
116 }
117
118 /**
119  * Load PEM key from file
120  *
121  * @param key where to store the data
122  * @param keyfile path to the PEM file
123  * @return #GNUNET_OK on success
124  */
125 static int
126 load_key_from_file (gnutls_x509_privkey_t key,
127                     const char* keyfile)
128 {
129   gnutls_datum_t key_data;
130   int ret;
131
132   key_data.data = load_file (keyfile,
133                              &key_data.size);
134   if (NULL == key_data.data)
135     return GNUNET_SYSERR;
136   ret = gnutls_x509_privkey_import (key, &key_data,
137                                     GNUTLS_X509_FMT_PEM);
138   if (GNUTLS_E_SUCCESS != ret)
139   {
140     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
141                 _("Unable to import private key from file `%s'\n"),
142                 keyfile);
143   }
144   GNUNET_free_non_null (key_data.data);
145   return (GNUTLS_E_SUCCESS != ret) ? GNUNET_SYSERR : GNUNET_OK;
146 }
147
148 /**
149  * Load cert from file
150  *
151  * @param crt struct to store data in
152  * @param certfile path to pem file
153  * @return #GNUNET_OK on success
154  */
155 static int
156 load_cert_from_file (gnutls_x509_crt_t crt,
157                      const char* certfile)
158 {
159   gnutls_datum_t cert_data;
160   int ret;
161
162   cert_data.data = load_file (certfile,
163                               &cert_data.size);
164   if (NULL == cert_data.data)
165     return GNUNET_SYSERR;
166   ret = gnutls_x509_crt_import (crt,
167                                 &cert_data,
168                                 GNUTLS_X509_FMT_PEM);
169   if (GNUTLS_E_SUCCESS != ret)
170   {
171     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
172                 _("Unable to import certificate from `%s'\n"),
173                 certfile);
174   }
175   GNUNET_free_non_null (cert_data.data);
176   return (GNUTLS_E_SUCCESS != ret) ? GNUNET_SYSERR : GNUNET_OK;
177 }
178
179 static size_t
180 copy_buffer (void *ptr, size_t size, size_t nmemb, void *ctx)
181 {
182   struct CBC *cbc = ctx;
183
184   if (cbc->pos + size * nmemb > sizeof(cbc->buf))
185     return 0;                   /* overflow */
186   GNUNET_memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
187   cbc->pos += size * nmemb;
188   return size * nmemb;
189 }
190
191
192 static int
193 mhd_ahc (void *cls,
194           struct MHD_Connection *connection,
195           const char *url,
196           const char *method,
197           const char *version,
198           const char *upload_data, size_t *upload_data_size,
199           void **unused)
200 {
201   static int ptr;
202   struct MHD_Response *response;
203   int ret;
204
205   if (0 != strcmp ("GET", method))
206     return MHD_NO;              /* unexpected method */
207   if (&ptr != *unused)
208   {
209     *unused = &ptr;
210     return MHD_YES;
211   }
212   *unused = NULL;
213   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "MHD sends respose for request to URL `%s'\n", url);
214   response = MHD_create_response_from_buffer (strlen (url),
215                                               (void *) url,
216                                               MHD_RESPMEM_MUST_COPY);
217   ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
218   MHD_destroy_response (response);
219   if (ret == MHD_NO) {
220     global_ret = 1;
221     abort ();
222   }
223   global_ret = 0;
224   return ret;
225 }
226
227
228 static void
229 do_shutdown ()
230 {
231   if (mhd_task_id != NULL)
232   {
233     GNUNET_SCHEDULER_cancel (mhd_task_id);
234     mhd_task_id = NULL;
235   }
236   if (curl_task_id != NULL)
237   {
238     GNUNET_SCHEDULER_cancel (curl_task_id);
239     curl_task_id = NULL;
240   }
241   if (NULL != mhd)
242   {
243     MHD_stop_daemon (mhd);
244     mhd = NULL;
245   }
246   GNUNET_free_non_null (url);
247
248   if (NULL != proxy_proc)
249     {
250       (void) GNUNET_OS_process_kill (proxy_proc, SIGKILL);
251       GNUNET_assert (GNUNET_OK == GNUNET_OS_process_wait (proxy_proc));
252       GNUNET_OS_process_destroy (proxy_proc);
253       proxy_proc = NULL;
254     }
255   url = NULL;
256   GNUNET_SCHEDULER_shutdown ();
257 }
258
259
260 /**
261  * Function to run the HTTP client.
262  */
263 static void
264 curl_main (void);
265
266
267 static void
268 curl_task (void *cls)
269 {
270   curl_task_id = NULL;
271   curl_main ();
272 }
273
274
275 static void
276 curl_main ()
277 {
278   fd_set rs;
279   fd_set ws;
280   fd_set es;
281   int max;
282   struct GNUNET_NETWORK_FDSet nrs;
283   struct GNUNET_NETWORK_FDSet nws;
284   struct GNUNET_TIME_Relative delay;
285   long timeout;
286   int running;
287   struct CURLMsg *msg;
288
289   max = 0;
290   FD_ZERO (&rs);
291   FD_ZERO (&ws);
292   FD_ZERO (&es);
293   curl_multi_perform (multi, &running);
294   if (running == 0)
295   {
296     GNUNET_assert (NULL != (msg = curl_multi_info_read (multi, &running)));
297     if (msg->msg == CURLMSG_DONE)
298     {
299       if (msg->data.result != CURLE_OK)
300       {
301         fprintf (stderr,
302                  "%s failed at %s:%d: `%s'\n",
303                  "curl_multi_perform",
304                  __FILE__,
305                  __LINE__, curl_easy_strerror (msg->data.result));
306         global_ret = 1;
307       }
308     }
309     curl_multi_remove_handle (multi, curl);
310     curl_multi_cleanup (multi);
311     curl_easy_cleanup (curl);
312     curl = NULL;
313     multi = NULL;
314     if (cbc.pos != strlen ("/hello_world"))
315     {
316       GNUNET_break (0);
317       global_ret = 2;
318     }
319     if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
320     {
321       GNUNET_break (0);
322       global_ret = 3;
323     }
324     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Download complete, shutting down!\n");
325     do_shutdown ();
326     return;
327   }
328   GNUNET_assert (CURLM_OK == curl_multi_fdset (multi, &rs, &ws, &es, &max));
329   if ( (CURLM_OK != curl_multi_timeout (multi, &timeout)) ||
330        (-1 == timeout) )
331     delay = GNUNET_TIME_UNIT_SECONDS;
332   else
333     delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, (unsigned int) timeout);
334   GNUNET_NETWORK_fdset_copy_native (&nrs,
335                                     &rs,
336                                     max + 1);
337   GNUNET_NETWORK_fdset_copy_native (&nws,
338                                     &ws,
339                                     max + 1);
340   curl_task_id = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
341                                               delay,
342                                               &nrs,
343                                               &nws,
344                                               &curl_task,
345                                               NULL);
346 }
347
348
349 static void
350 start_curl (void *cls)
351 {
352   GNUNET_asprintf (&url,
353                    "https://%s:%d/hello_world",
354                    TEST_DOMAIN, port);
355   curl = curl_easy_init ();
356   curl_easy_setopt (curl, CURLOPT_URL, url);
357   //curl_easy_setopt (curl, CURLOPT_URL, "https://127.0.0.1:8443/hello_world");
358   curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, &copy_buffer);
359   curl_easy_setopt (curl, CURLOPT_WRITEDATA, &cbc);
360   curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1);
361   curl_easy_setopt (curl, CURLOPT_TIMEOUT, 150L);
362   curl_easy_setopt (curl, CURLOPT_CONNECTTIMEOUT, 15L);
363   curl_easy_setopt (curl, CURLOPT_NOSIGNAL, 1);
364   curl_easy_setopt (curl, CURLOPT_CAINFO, cafile_opt);
365   //curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0L);
366   //curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0L);
367   curl_easy_setopt (curl, CURLOPT_PROXY, "socks5h://127.0.0.1:7777");
368
369   multi = curl_multi_init ();
370   GNUNET_assert (multi != NULL);
371   GNUNET_assert (CURLM_OK == curl_multi_add_handle (multi, curl));
372   GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Beginning HTTP download from `%s'\n", url);
373   curl_main ();
374 }
375
376
377 /**
378  * Callback invoked from the namestore service once record is
379  * created.
380  *
381  * @param cls closure
382  * @param af address family, AF_INET or AF_INET6; AF_UNSPEC on error;
383  *                will match 'result_af' from the request
384  * @param address IP address (struct in_addr or struct in_addr6, depending on 'af')
385  *                that the VPN allocated for the redirection;
386  *                traffic to this IP will now be redirected to the
387  *                specified target peer; NULL on error
388  */
389 static void
390 commence_testing (void *cls)
391 {
392   curl_task_id =
393     GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
394                                   &start_curl, NULL);
395 }
396
397
398 /**
399  * Function to keep the HTTP server running.
400  */
401 static void
402 mhd_main (void);
403
404
405 static void
406 mhd_task (void *cls)
407 {
408   mhd_task_id = NULL;
409   MHD_run (mhd);
410   mhd_main ();
411 }
412
413
414 static void
415 mhd_main ()
416 {
417   struct GNUNET_NETWORK_FDSet nrs;
418   struct GNUNET_NETWORK_FDSet nws;
419   fd_set rs;
420   fd_set ws;
421   fd_set es;
422   int max_fd;
423   unsigned MHD_LONG_LONG timeout;
424   struct GNUNET_TIME_Relative delay;
425
426   GNUNET_assert (NULL == mhd_task_id);
427   FD_ZERO (&rs);
428   FD_ZERO (&ws);
429   FD_ZERO (&es);
430   max_fd = -1;
431   GNUNET_assert (MHD_YES ==
432                  MHD_get_fdset (mhd, &rs, &ws, &es, &max_fd));
433   if (MHD_YES == MHD_get_timeout (mhd, &timeout))
434     delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
435                                            (unsigned int) timeout);
436   else
437     delay = GNUNET_TIME_UNIT_FOREVER_REL;
438   GNUNET_NETWORK_fdset_copy_native (&nrs,
439                                     &rs,
440                                     max_fd + 1);
441   GNUNET_NETWORK_fdset_copy_native (&nws,
442                                     &ws,
443                                     max_fd + 1);
444   mhd_task_id = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
445                                              delay,
446                                              &nrs,
447                                              &nws,
448                                              &mhd_task,
449                                              NULL);
450 }
451
452
453 /**
454  * Main function that will be run
455  *
456  * @param cls closure
457  * @param args remaining command-line arguments
458  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
459  * @param c configuration
460  */
461 static void
462 run (void *cls,
463      char *const *args,
464      const char *cfgfile,
465      const struct GNUNET_CONFIGURATION_Handle *c)
466 {
467   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
468               "Using `%s' as CA\n",
469               cafile_srv);
470   char cert[MAX_PEM_SIZE];
471   char key[MAX_PEM_SIZE];
472   size_t key_buf_size;
473   size_t cert_buf_size;
474
475   gnutls_global_init ();
476   gnutls_x509_crt_init (&proxy_cert);
477   gnutls_x509_privkey_init (&proxy_key);
478
479   if ( (GNUNET_OK !=
480         load_cert_from_file (proxy_cert,
481                              cafile_srv)) ||
482        (GNUNET_OK !=
483         load_key_from_file (proxy_key,
484                             cafile_srv)) )
485   {
486     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
487                 _("Failed to load X.509 key and certificate from `%s'\n"),
488                 cafile_srv);
489     gnutls_x509_crt_deinit (proxy_cert);
490     gnutls_x509_privkey_deinit (proxy_key);
491     gnutls_global_deinit ();
492     return;
493   }
494   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
495                                  NULL);
496   key_buf_size = sizeof (key);
497   cert_buf_size = sizeof (cert);
498   gnutls_x509_crt_export (proxy_cert,
499                           GNUTLS_X509_FMT_PEM,
500                           cert,
501                           &cert_buf_size);
502   gnutls_x509_privkey_export (proxy_key,
503                               GNUTLS_X509_FMT_PEM,
504                               key,
505                               &key_buf_size);
506   mhd = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_SSL | MHD_ALLOW_SUSPEND_RESUME, port,
507                           NULL, NULL,
508                           &mhd_ahc, NULL,
509                           MHD_OPTION_HTTPS_MEM_KEY, key,
510                           MHD_OPTION_HTTPS_MEM_CERT, cert,
511                           MHD_OPTION_END);
512   GNUNET_assert (NULL != mhd);
513   mhd_main ();
514
515   GNUNET_SCHEDULER_add_now (&commence_testing,
516                             NULL);
517 }
518
519 int
520 main (int argc, char *const *argv)
521 {
522   struct GNUNET_GETOPT_CommandLineOption options[] = {
523     GNUNET_GETOPT_option_uint16 ('p',
524                                  "port",
525                                  NULL,
526                                  gettext_noop ("listen on specified port (default: 7777)"),
527                                  &port),
528     GNUNET_GETOPT_option_string ('A',
529                                  "curlcert",
530                                  NULL,
531                                  gettext_noop ("pem file to use as CA"),
532                                  &cafile_opt),
533     GNUNET_GETOPT_option_string ('S',
534                                  "servercert",
535                                  NULL,
536                                  gettext_noop ("pem file to use for the server"),
537                                  &cafile_srv),
538
539     GNUNET_GETOPT_OPTION_END
540   };
541
542   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
543   {
544     fprintf (stderr, "failed to initialize curl\n");
545     return 2;
546   }
547   if (GNUNET_OK !=
548       GNUNET_STRINGS_get_utf8_args (argc, argv,
549                                     &argc, &argv))
550     return 2;
551   GNUNET_log_setup ("gnunet-gns-proxy-test",
552                     "WARNING",
553                     NULL);
554   if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv,
555                                        "gnunet-gns-proxy-test",
556                                        _("GNUnet GNS proxy test"),
557                                        options,
558                                        &run, NULL))
559     return 1;
560   GNUNET_free_non_null ((char *) argv);
561   return global_ret;
562 }
563
564 /* end of test_gns_vpn.c */