1/*
2 This file is part of libmicrohttpd
3 Copyright (C) 2007 Christian Grothoff
4
5 libmicrohttpd 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 2, or (at your
8 option) any later version.
9
10 libmicrohttpd 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 libmicrohttpd; 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 tls_test_common.c
23 * @brief  Common tls test functions
24 * @author Sagie Amir
25 */
26#include "tls_test_common.h"
27#include "tls_test_keys.h"
28
29
30int curl_check_version (const char *req_version, ...);
31
32FILE *
33setup_ca_cert ()
34{
35  FILE *cert_fd;
36
37  if (NULL == (cert_fd = fopen (ca_cert_file_name, "wb+")))
38    {
39      fprintf (stderr, "Error: failed to open `%s': %s\n",
40               ca_cert_file_name, strerror (errno));
41      return NULL;
42    }
43  if (fwrite (ca_cert_pem, sizeof (char), strlen (ca_cert_pem) + 1, cert_fd)
44      != strlen (ca_cert_pem) + 1)
45    {
46      fprintf (stderr, "Error: failed to write `%s. %s'\n",
47               ca_cert_file_name, strerror (errno));
48      fclose (cert_fd);
49      return NULL;
50    }
51  if (fflush (cert_fd))
52    {
53      fprintf (stderr, "Error: failed to flush ca cert file stream. %s\n",
54               strerror (errno));
55      fclose (cert_fd);
56      return NULL;
57    }
58  return cert_fd;
59}
60
61
62/*
63 * test HTTPS transfer
64 */
65int
66test_daemon_get (void *cls,
67		 const char *cipher_suite, int proto_version,
68		 int port,
69		 int ver_peer)
70{
71  CURL *c;
72  struct CBC cbc;
73  CURLcode errornum;
74  char url[255];
75  size_t len;
76
77  len = strlen (test_data);
78  if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
79    {
80      fprintf (stderr, MHD_E_MEM);
81      return -1;
82    }
83  cbc.size = len;
84  cbc.pos = 0;
85
86  /* construct url - this might use doc_path */
87  gen_test_file_url (url, port);
88
89  c = curl_easy_init ();
90#if DEBUG_HTTPS_TEST
91  curl_easy_setopt (c, CURLOPT_VERBOSE, 1);
92#endif
93  curl_easy_setopt (c, CURLOPT_URL, url);
94  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
95  curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L);
96  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L);
97  curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
98  curl_easy_setopt (c, CURLOPT_FILE, &cbc);
99
100  /* TLS options */
101  curl_easy_setopt (c, CURLOPT_SSLVERSION, proto_version);
102  curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, cipher_suite);
103
104  /* perform peer authentication */
105  /* TODO merge into send_curl_req */
106  curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, ver_peer);
107  curl_easy_setopt (c, CURLOPT_CAINFO, ca_cert_file_name);
108  curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
109  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
110
111  /* NOTE: use of CONNECTTIMEOUT without also
112     setting NOSIGNAL results in really weird
113     crashes on my system! */
114  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
115  if (CURLE_OK != (errornum = curl_easy_perform (c)))
116    {
117      fprintf (stderr, "curl_easy_perform failed: `%s'\n",
118               curl_easy_strerror (errornum));
119      curl_easy_cleanup (c);
120      free (cbc.buf);
121      return errornum;
122    }
123
124  curl_easy_cleanup (c);
125
126  if (memcmp (cbc.buf, test_data, len) != 0)
127    {
128      fprintf (stderr, "Error: local file & received file differ.\n");
129      free (cbc.buf);
130      return -1;
131    }
132
133  free (cbc.buf);
134  return 0;
135}
136
137
138void
139print_test_result (int test_outcome, char *test_name)
140{
141#if 0
142  if (test_outcome != 0)
143    fprintf (stderr, "running test: %s [fail]\n", test_name);
144  else
145    fprintf (stdout, "running test: %s [pass]\n", test_name);
146#endif
147}
148
149size_t
150copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
151{
152  struct CBC *cbc = ctx;
153
154  if (cbc->pos + size * nmemb > cbc->size)
155    return 0;                   /* overflow */
156  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
157  cbc->pos += size * nmemb;
158  return size * nmemb;
159}
160
161/**
162 *  HTTP access handler call back
163 */
164int
165http_ahc (void *cls, struct MHD_Connection *connection,
166          const char *url, const char *method, const char *upload_data,
167          const char *version, size_t *upload_data_size, void **ptr)
168{
169  static int aptr;
170  struct MHD_Response *response;
171  int ret;
172
173  if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
174    return MHD_NO;              /* unexpected method */
175  if (&aptr != *ptr)
176    {
177      /* do never respond on first call */
178      *ptr = &aptr;
179      return MHD_YES;
180    }
181  *ptr = NULL;                  /* reset when done */
182  response = MHD_create_response_from_buffer (strlen (test_data),
183					      (void *) test_data,
184					      MHD_RESPMEM_PERSISTENT);
185  ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
186  MHD_destroy_response (response);
187  return ret;
188}
189
190/* HTTP access handler call back */
191int
192http_dummy_ahc (void *cls, struct MHD_Connection *connection,
193                const char *url, const char *method, const char *upload_data,
194                const char *version, size_t *upload_data_size,
195                void **ptr)
196{
197  return 0;
198}
199
200/**
201 * send a test http request to the daemon
202 * @param url
203 * @param cbc - may be null
204 * @param cipher_suite
205 * @param proto_version
206 * @return
207 */
208/* TODO have test wrap consider a NULL cbc */
209int
210send_curl_req (char *url, struct CBC * cbc, const char *cipher_suite,
211               int proto_version)
212{
213  CURL *c;
214  CURLcode errornum;
215  c = curl_easy_init ();
216#if DEBUG_HTTPS_TEST
217  curl_easy_setopt (c, CURLOPT_VERBOSE, CURL_VERBOS_LEVEL);
218#endif
219  curl_easy_setopt (c, CURLOPT_URL, url);
220  curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
221  curl_easy_setopt (c, CURLOPT_TIMEOUT, 60L);
222  curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 60L);
223
224  if (cbc != NULL)
225    {
226      curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
227      curl_easy_setopt (c, CURLOPT_FILE, cbc);
228    }
229
230  /* TLS options */
231  curl_easy_setopt (c, CURLOPT_SSLVERSION, proto_version);
232  curl_easy_setopt (c, CURLOPT_SSL_CIPHER_LIST, cipher_suite);
233
234  /* currently skip any peer authentication */
235  curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0);
236  curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 0);
237
238  curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
239
240  /* NOTE: use of CONNECTTIMEOUT without also
241     setting NOSIGNAL results in really weird
242     crashes on my system! */
243  curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
244  if (CURLE_OK != (errornum = curl_easy_perform (c)))
245    {
246      fprintf (stderr, "curl_easy_perform failed: `%s'\n",
247               curl_easy_strerror (errornum));
248      curl_easy_cleanup (c);
249      return errornum;
250    }
251  curl_easy_cleanup (c);
252
253  return CURLE_OK;
254}
255
256
257/**
258 * compile test file url pointing to the current running directory path
259 *
260 * @param url - char buffer into which the url is compiled
261 * @param port port to use for the test
262 * @return -1 on error
263 */
264int
265gen_test_file_url (char *url, int port)
266{
267  int ret = 0;
268  char *doc_path;
269  size_t doc_path_len;
270  /* setup test file path, url */
271  doc_path_len = PATH_MAX > 4096 ? 4096 : PATH_MAX;
272  if (NULL == (doc_path = malloc (doc_path_len)))
273    {
274      fprintf (stderr, MHD_E_MEM);
275      return -1;
276    }
277  if (getcwd (doc_path, doc_path_len) == NULL)
278    {
279      fprintf (stderr, "Error: failed to get working directory. %s\n",
280               strerror (errno));
281      ret = -1;
282    }
283#ifdef WINDOWS
284  {
285    int i;
286    for (i = 0; i < doc_path_len; i++)
287    {
288      if (doc_path[i] == 0)
289        break;
290      if (doc_path[i] == '\\')
291      {
292        doc_path[i] = '/';
293      }
294      if (doc_path[i] != ':')
295        continue;
296      if (i == 0)
297        break;
298      doc_path[i] = doc_path[i - 1];
299      doc_path[i - 1] = '/';
300    }
301  }
302#endif
303  /* construct url - this might use doc_path */
304  if (sprintf (url, "%s:%d%s/%s", "https://127.0.0.1", port,
305               doc_path, "urlpath") < 0)
306    ret = -1;
307
308  free (doc_path);
309  return ret;
310}
311
312/**
313 * test HTTPS file transfer
314 */
315int
316test_https_transfer (void *cls, const char *cipher_suite, int proto_version)
317{
318  int len;
319  int ret = 0;
320  struct CBC cbc;
321  char url[255];
322
323  len = strlen (test_data);
324  if (NULL == (cbc.buf = malloc (sizeof (char) * len)))
325    {
326      fprintf (stderr, MHD_E_MEM);
327      return -1;
328    }
329  cbc.size = len;
330  cbc.pos = 0;
331
332  if (gen_test_file_url (url, DEAMON_TEST_PORT))
333    {
334      ret = -1;
335      goto cleanup;
336    }
337
338  if (CURLE_OK != send_curl_req (url, &cbc, cipher_suite, proto_version))
339    {
340      ret = -1;
341      goto cleanup;
342    }
343
344  /* compare test file & daemon responce */
345  if ( (len != strlen (test_data)) ||
346       (memcmp (cbc.buf,
347		test_data,
348		len) != 0) )
349    {
350      fprintf (stderr, "Error: local file & received file differ.\n");
351      ret = -1;
352    }
353cleanup:
354  free (cbc.buf);
355  return ret;
356}
357
358/**
359 * setup test case
360 *
361 * @param d
362 * @param daemon_flags
363 * @param arg_list
364 * @return
365 */
366int
367setup_testcase (struct MHD_Daemon **d, int daemon_flags, va_list arg_list)
368{
369  *d = MHD_start_daemon_va (daemon_flags, DEAMON_TEST_PORT,
370                            NULL, NULL, &http_ahc, NULL, arg_list);
371
372  if (*d == NULL)
373    {
374      fprintf (stderr, MHD_E_SERVER_INIT);
375      return -1;
376    }
377
378  return 0;
379}
380
381void
382teardown_testcase (struct MHD_Daemon *d)
383{
384  MHD_stop_daemon (d);
385}
386
387int
388setup_session (gnutls_session_t * session,
389               gnutls_datum_t * key,
390               gnutls_datum_t * cert,
391	       gnutls_certificate_credentials_t * xcred)
392{
393  int ret;
394  const char *err_pos;
395
396  gnutls_certificate_allocate_credentials (xcred);
397  key->size = strlen (srv_key_pem) + 1;
398  key->data = malloc (key->size);
399  if (NULL == key->data)
400     {
401       gnutls_certificate_free_credentials (*xcred);
402	return -1;
403     }
404  memcpy (key->data, srv_key_pem, key->size);
405  cert->size = strlen (srv_self_signed_cert_pem) + 1;
406  cert->data = malloc (cert->size);
407  if (NULL == cert->data)
408    {
409        gnutls_certificate_free_credentials (*xcred);
410	free (key->data);
411	return -1;
412    }
413  memcpy (cert->data, srv_self_signed_cert_pem, cert->size);
414  gnutls_certificate_set_x509_key_mem (*xcred, cert, key,
415				       GNUTLS_X509_FMT_PEM);
416  gnutls_init (session, GNUTLS_CLIENT);
417  ret = gnutls_priority_set_direct (*session,
418				    "NORMAL", &err_pos);
419  if (ret < 0)
420    {
421       gnutls_deinit (*session);
422       gnutls_certificate_free_credentials (*xcred);
423       free (key->data);
424       return -1;
425    }
426  gnutls_credentials_set (*session,
427			  GNUTLS_CRD_CERTIFICATE,
428			  *xcred);
429  return 0;
430}
431
432int
433teardown_session (gnutls_session_t session,
434                  gnutls_datum_t * key,
435                  gnutls_datum_t * cert,
436                  gnutls_certificate_credentials_t xcred)
437{
438  free (key->data);
439  key->data = NULL;
440  key->size = 0;
441  free (cert->data);
442  cert->data = NULL;
443  cert->size = 0;
444  gnutls_deinit (session);
445  gnutls_certificate_free_credentials (xcred);
446  return 0;
447}
448
449/* TODO test_wrap: change sig to (setup_func, test, va_list test_arg) */
450int
451test_wrap (const char *test_name, int
452           (*test_function) (void * cls, const char *cipher_suite,
453                             int proto_version), void * cls,
454           int daemon_flags, const char *cipher_suite, int proto_version, ...)
455{
456  int ret;
457  va_list arg_list;
458  struct MHD_Daemon *d;
459
460  va_start (arg_list, proto_version);
461  if (setup_testcase (&d, daemon_flags, arg_list) != 0)
462    {
463      va_end (arg_list);
464      fprintf (stderr, "Failed to setup testcase %s\n", test_name);
465      return -1;
466    }
467#if 0
468  fprintf (stdout, "running test: %s ", test_name);
469#endif
470  ret = test_function (NULL, cipher_suite, proto_version);
471#if 0
472  if (ret == 0)
473    {
474      fprintf (stdout, "[pass]\n");
475    }
476  else
477    {
478      fprintf (stdout, "[fail]\n");
479    }
480#endif
481  teardown_testcase (d);
482  va_end (arg_list);
483  return ret;
484}
485