1 /* check-sources:disable-copyright-check */
2 /* unit test the code in droplet.c */
3 #include <sys/types.h>
4 #include <stdio.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <stdlib.h>
9 #include <check.h>
10 #include <droplet.h>
11 #include <droplet/backend.h>
12 #include <arpa/inet.h>
13 #include <errno.h>
14 
15 #include "testutils.h"
16 #include "utest_main.h"
17 
18 #ifdef __linux__
19 
20 static int bound_sock = -1;
21 
get_bound_port(char * host,int maxlen)22 static int get_bound_port(char* host, int maxlen)
23 {
24   struct sockaddr_in sin;
25   socklen_t len;
26   int sock;
27   int r;
28 
29   /* only one at a time */
30   fail_if(bound_sock >= 0, NULL);
31 
32   /* create a TCP socket */
33   sock = socket(PF_INET, SOCK_STREAM, 0);
34   fail_if(sock < 0, strerror(errno));
35 
36   /* bind the socket to some port on localhost */
37   memset(&sin, 0, sizeof(sin));
38   sin.sin_family = AF_INET;
39   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
40   sin.sin_port = 0; /* kernel allocates */
41   r = bind(sock, (struct sockaddr*)&sin, sizeof(sin));
42   fail_if(r < 0, strerror(errno));
43 
44   /* note: we never listen() on the socket, so attempts
45    * to connect() should fail immediately with ECONNREFUSED */
46 
47   /* find out what port the kernel chose for us */
48   len = sizeof(sin);
49   r = getsockname(sock, (struct sockaddr*)&sin, &len);
50   fail_if(r < 0, strerror(errno));
51 
52   bound_sock = sock;
53 
54   snprintf(host, maxlen, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
55   return 0;
56 }
57 
release_bound_port(void)58 static void release_bound_port(void)
59 {
60   if (bound_sock >= 0) {
61     close(bound_sock);
62     bound_sock = -1;
63   }
64 }
65 
setup(void)66 static void setup(void)
67 {
68   /* Make sure there's a new unique directory which
69    * is returned as our home directory by getpwuid() */
70   home = make_unique_directory();
71 
72   dpl_init();
73 }
74 
teardown(void)75 static void teardown(void)
76 {
77   release_bound_port();
78 
79   rmtree(home);
80   free(home);
81   home = NULL;
82 }
83 
START_TEST(ctx_new_defaults_test)84 START_TEST(ctx_new_defaults_test)
85 {
86   dpl_ctx_t* ctx;
87   char* dropdir;
88   char* profile;
89 
90   /* Make sure there's a directory ~/.droplet
91    * containing a file default.profile */
92   dropdir = make_sub_directory(home, ".droplet");
93   profile = write_file(dropdir, "default.profile",
94                        "host = localhost\n"
95                        "base_path = /polaroid\n");
96 
97   /* create a context with all defaults */
98   unsetenv("DPLDIR");
99   unsetenv("DPLPROFILE");
100   ctx = dpl_ctx_new(NULL, NULL);
101   dpl_assert_ptr_not_null(ctx);
102   dpl_assert_str_eq(ctx->droplet_dir, dropdir);
103   dpl_assert_str_eq(ctx->profile_name, "default");
104   dpl_assert_str_eq(ctx->base_path, "/polaroid");
105 
106   dpl_ctx_free(ctx);
107   free(dropdir);
108   free(profile);
109 }
110 END_TEST
111 
START_TEST(ctx_new_dropdir_test)112 START_TEST(ctx_new_dropdir_test)
113 {
114   dpl_ctx_t* ctx;
115   char* dropdir;
116   char* profile;
117 
118   /* Make sure there's a new unique directory,
119    * which is *NOT* ~/.droplet, and contains a
120    * file default.profile
121    * random word courtesy http://hipsteripsum.me/ */
122   dropdir = make_sub_directory(home, "williamsburg");
123   profile = write_file(dropdir, "default.profile",
124                        "host = localhost\n"
125                        "base_path = /polaroid\n");
126 
127   /* create a context with all droplet dir named via the environment */
128   setenv("DPLDIR", dropdir, /*overwrite*/ 1);
129   unsetenv("DPLPROFILE");
130   ctx = dpl_ctx_new(NULL, NULL);
131   dpl_assert_ptr_not_null(ctx);
132   dpl_assert_str_eq(ctx->droplet_dir, dropdir);
133   dpl_assert_str_eq(ctx->profile_name, "default");
134   dpl_assert_str_eq(ctx->base_path, "/polaroid");
135 
136   dpl_ctx_free(ctx);
137   free(dropdir);
138   free(profile);
139 }
140 END_TEST
141 
START_TEST(ctx_new_dropdir_profile_test)142 START_TEST(ctx_new_dropdir_profile_test)
143 {
144   dpl_ctx_t* ctx;
145   char* dropdir;
146   char* profile;
147 
148   /* Make sure there's a new unique directory,
149    * which is *NOT* ~/.droplet, and contains a
150    * file profile *NOT* called default.profile. */
151   dropdir = make_sub_directory(home, "quinoa");
152   profile = write_file(dropdir, "sriracha.profile",
153                        "host = localhost\n"
154                        "base_path = /polaroid\n");
155 
156   /* create a context with profile named via the environment */
157   setenv("DPLDIR", dropdir, /*overwrite*/ 1);
158   setenv("DPLPROFILE", "sriracha", /*overwrite*/ 1);
159   ctx = dpl_ctx_new(NULL, NULL);
160   dpl_assert_ptr_not_null(ctx);
161   dpl_assert_str_eq(ctx->droplet_dir, dropdir);
162   dpl_assert_str_eq(ctx->profile_name, "sriracha");
163   dpl_assert_str_eq(ctx->base_path, "/polaroid");
164 
165   dpl_ctx_free(ctx);
166   free(dropdir);
167   free(profile);
168 }
169 END_TEST
170 
START_TEST(ctx_new_from_dict_test)171 START_TEST(ctx_new_from_dict_test)
172 {
173   dpl_ctx_t* ctx;
174   dpl_dict_t* profile;
175   char* dropdir;
176 
177   /* At this point we have a pretend ~ with NO
178    * .droplet/ directory in it.  We don't create
179    * a dropdir on disk at all, nor a profile, it's
180    * all in memory. */
181   dropdir = strconcat(home, "/trust-fund", (char*)NULL); /* never created */
182 
183   profile = dpl_dict_new(13);
184   dpl_assert_ptr_not_null(profile);
185   dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "host", "localhost", 0));
186   dpl_assert_int_eq(DPL_SUCCESS,
187                     dpl_dict_add(profile, "base_path", "/polaroid", 0));
188   dpl_assert_int_eq(DPL_SUCCESS,
189                     dpl_dict_add(profile, "droplet_dir", dropdir, 0));
190   dpl_assert_int_eq(DPL_SUCCESS,
191                     dpl_dict_add(profile, "profile_name", "viral", 0));
192   /* need this to disable the event log, otherwise the droplet_dir needs to
193    * exist */
194   dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "pricing_dir", "", 0));
195 
196   /* create a context with all defaults */
197   unsetenv("DPLDIR");
198   unsetenv("DPLPROFILE");
199   ctx = dpl_ctx_new_from_dict(profile);
200   dpl_assert_ptr_not_null(ctx);
201   dpl_assert_str_eq(ctx->droplet_dir, dropdir);
202   dpl_assert_str_eq(ctx->profile_name, "viral");
203   dpl_assert_str_eq(ctx->base_path, "/polaroid");
204 
205   dpl_ctx_free(ctx);
206   dpl_dict_free(profile);
207   free(dropdir);
208 }
209 END_TEST
210 
211 /* actually set some non-default parameters to check they're parsed */
START_TEST(ctx_new_params_test)212 START_TEST(ctx_new_params_test)
213 {
214   dpl_ctx_t* ctx;
215   char* dropdir;
216   char* profile;
217   char* ssl_cert_file;
218   char* ssl_key_file;
219   char* ssl_ca_cert_file;
220   char* profdata;
221   char* pricing;
222   static const char ssl_cert[] =
223   /* This is actually a chain, starting with the client cert
224    * and ending with the CA cert, in that order */
225 #  include "ssldata/client-cert.c"
226 #  include "ssldata/demoCA/cacert.c"
227       ;
228   static const char ssl_key[] =
229 #  include "ssldata/client.c"
230       ;
231   static const char ssl_ca_cert[] =
232 #  include "ssldata/demoCA/cacert.c"
233       ;
234 
235   /* Make sure there's a directory ~/.droplet
236    * containing a file default.profile */
237   dropdir = make_sub_directory(home, ".droplet");
238   ssl_cert_file = write_file(dropdir, "disrupt.pem", ssl_cert);
239   ssl_key_file = write_file(dropdir, "fingerstache.pem", ssl_key);
240   ssl_ca_cert_file = write_file(home, "stumptown.pem", ssl_ca_cert);
241   pricing = write_file(dropdir, "pitchfork.pricing", "# this file is empty\n");
242   profdata = strconcat(
243       "use_https = true\n"
244       "host = localhost\n"
245       "blacklist_expiretime = 42\n"
246       "header_size = 12345\n"
247       "base_path = /polaroid\n"
248       "access_key = letterpress\n"
249       "secret_key = freegan\n"
250       "ssl_cert_file = disrupt.pem\n"
251       "ssl_key_file = fingerstache.pem\n"
252       "ssl_password = mixtape\n"
253       "ssl_ca_list = ",
254       home,
255       "/stumptown.pem\n"
256       "pricing = pitchfork\n"
257       "read_buf_size = 8765\n"
258       "encrypt_key = distillery\n"
259       "backend = swift\n"
260       "encode_slashes = true\n",
261       (char*)NULL);
262   profile = write_file(dropdir, "default.profile", profdata);
263 
264   /* create a context with all defaults */
265   unsetenv("DPLDIR");
266   unsetenv("DPLPROFILE");
267   ctx = dpl_ctx_new(NULL, NULL);
268   dpl_assert_ptr_not_null(ctx);
269   dpl_assert_str_eq(ctx->droplet_dir, dropdir);
270   dpl_assert_str_eq(ctx->profile_name, "default");
271   dpl_assert_str_eq(ctx->base_path, "/polaroid");
272   dpl_assert_bit_eq(ctx->use_https, 1);
273   dpl_assert_int_eq(ctx->blacklist_expiretime, 42);
274   dpl_assert_int_eq(ctx->header_size, 12345);
275   dpl_assert_str_eq(ctx->access_key, "letterpress");
276   dpl_assert_str_eq(ctx->secret_key, "freegan");
277   dpl_assert_str_eq(ctx->ssl_cert_file, ssl_cert_file);
278   dpl_assert_str_eq(ctx->ssl_key_file, ssl_key_file);
279   dpl_assert_str_eq(ctx->ssl_password, "mixtape");
280   dpl_assert_str_eq(ctx->ssl_ca_list, ssl_ca_cert_file);
281   dpl_assert_str_eq(ctx->pricing, "pitchfork");
282   dpl_assert_ptr_null(ctx->pricing_dir);
283   dpl_assert_int_eq(ctx->read_buf_size, 8765);
284   dpl_assert_str_eq(ctx->encrypt_key, "distillery");
285   dpl_assert_str_eq(ctx->backend->name, "swift");
286   dpl_assert_bit_eq(ctx->encode_slashes, 1);
287 
288   dpl_ctx_free(ctx);
289   free(dropdir);
290   free(profile);
291   free(ssl_cert_file);
292   free(ssl_key_file);
293   free(ssl_ca_cert_file);
294   free(pricing);
295   free(profdata);
296 }
297 END_TEST
298 
redirect_stdio(FILE * fp,const char * name)299 static void redirect_stdio(FILE* fp, const char* name)
300 {
301   char* logfile;
302   int fd;
303 
304   logfile = strconcat(home, "/", name, ".log", (char*)NULL);
305   fd = open(logfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
306   if (fd < 0) {
307     perror(logfile);
308     fail();
309   }
310   fflush(fp);
311   if (dup2(fd, fileno(fp)) < 0) {
312     perror("dup2");
313     fail();
314   }
315   close(fd);
316   free(logfile);
317 }
318 
319 #  define MAXLOGGED 128
320 static struct {
321   dpl_log_level_t level;
322   char* message;
323 } logged[MAXLOGGED];
324 static int nlogged = 0;
325 
log_find(const char * needle)326 static int log_find(const char* needle)
327 {
328   int i;
329 
330   for (i = 0; i < nlogged; i++) {
331     if (strstr(logged[i].message, needle)) return i;
332   }
333 
334   return -1;
335 }
336 
log_func(dpl_ctx_t * ctx,dpl_log_level_t level,const char * message)337 static void log_func(dpl_ctx_t* ctx, dpl_log_level_t level, const char* message)
338 {
339   fail_unless(nlogged < MAXLOGGED);
340   logged[nlogged].level = level;
341   logged[nlogged].message = strdup(message);
342   nlogged++;
343 }
344 
START_TEST(logging_test)345 START_TEST(logging_test)
346 {
347   dpl_ctx_t* ctx;
348   dpl_dict_t* profile;
349   char* dropdir;
350   off_t logsize;
351   int nerrs;
352 
353   /* because libcheck forks a new process for each testcase, we
354    * can feel free to futz around with the process state like stderr */
355   redirect_stdio(stdout, "stdout");
356   redirect_stdio(stderr, "stderr");
357 
358   /* At this point we have a pretend ~ with NO
359    * .droplet/ directory in it.  We don't create
360    * a dropdir on disk at all, nor a profile, it's
361    * all in memory. */
362   dropdir = strconcat(home, "/trust-fund", (char*)NULL); /* never created */
363 
364   profile = dpl_dict_new(13);
365   dpl_assert_ptr_not_null(profile);
366   /* the host is an unparseable IPv4 address literal, which should
367    * result in a predicable error */
368   dpl_assert_int_eq(DPL_SUCCESS,
369                     dpl_dict_add(profile, "host", "123.456.789.012", 0));
370   dpl_assert_int_eq(DPL_SUCCESS,
371                     dpl_dict_add(profile, "droplet_dir", dropdir, 0));
372   dpl_assert_int_eq(DPL_SUCCESS,
373                     dpl_dict_add(profile, "profile_name", "viral", 0));
374   /* need this to disable the event log, otherwise the droplet_dir needs to
375    * exist */
376   dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "pricing_dir", "", 0));
377   unsetenv("DPLDIR");
378   unsetenv("DPLPROFILE");
379 
380   /* Create a context which will fail with a logged error message.
381    * By default error messages go to stderr only. */
382   dpl_assert_int_eq(0, file_size(stdout));
383   dpl_assert_int_eq(0, file_size(stderr));
384   ctx = dpl_ctx_new_from_dict(profile);
385   dpl_assert_ptr_null(ctx);
386   /* nothing went to stdout */
387   dpl_assert_int_eq(0, file_size(stdout));
388   /* something went to stderr */
389   logsize = file_size(stderr);
390   fail_unless(logsize > 0);
391 
392   /* Create a context which will fail with a logged error message.
393    * Calling dpl_set_log_func redirects errors to the given function.  */
394   dpl_assert_int_eq(nlogged, 0);
395   dpl_set_log_func(log_func);
396   ctx = dpl_ctx_new_from_dict(profile);
397   dpl_assert_ptr_null(ctx);
398   /* at least one error message logged */
399   dpl_assert_int_ne(nlogged, 0);
400   /* nothing went to stdout */
401   dpl_assert_int_eq(0, file_size(stdout));
402   /* nothing more went to stderr */
403   dpl_assert_int_eq(logsize, file_size(stderr));
404   /* errors mention the broken address */
405   dpl_assert_int_ne(-1, log_find("123.456.789.012"));
406 
407   /* Create a context which will fail with a logged error message.
408    * Calling dpl_set_log_func(NULL) redirects errors back to stderr. */
409   nerrs = nlogged;
410   dpl_set_log_func(NULL);
411   ctx = dpl_ctx_new_from_dict(profile);
412   dpl_assert_ptr_null(ctx);
413   /* no more errors went to the log function */
414   dpl_assert_int_eq(nerrs, nlogged);
415   /* nothing went to stdout */
416   dpl_assert_int_eq(0, file_size(stdout));
417   /* some more went to stderr */
418   fail_unless(file_size(stderr) > logsize);
419 
420   /* cleanup */
421   dpl_dict_free(profile);
422   free(dropdir);
423 }
424 END_TEST
425 
426 /* Create a context talking to a valid address on which nothing
427  * is listening.  This will fail during connect, and should log
428  * a message. */
START_TEST(connect_logging_test)429 START_TEST(connect_logging_test)
430 {
431   dpl_ctx_t* ctx;
432   dpl_dict_t* profile;
433   dpl_req_t* req;
434   dpl_conn_t* conn = NULL;
435   char* dropdir;
436   int r;
437   dpl_status_t s;
438   char host[128];
439 
440   /* because libcheck forks a new process for each testcase, we
441    * can feel free to futz around with the process state like stderr */
442   redirect_stdio(stdout, "stdout");
443   redirect_stdio(stderr, "stderr");
444 
445   /* At this point we have a pretend ~ with NO
446    * .droplet/ directory in it.  We don't create
447    * a dropdir on disk at all, nor a profile, it's
448    * all in memory. */
449   dropdir = strconcat(home, "/trust-fund", (char*)NULL); /* never created */
450 
451   r = get_bound_port(host, sizeof(host));
452   fail_if(r < 0, NULL);
453 
454   profile = dpl_dict_new(13);
455   dpl_assert_ptr_not_null(profile);
456   dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "host", host, 0));
457   dpl_assert_int_eq(DPL_SUCCESS,
458                     dpl_dict_add(profile, "droplet_dir", dropdir, 0));
459   dpl_assert_int_eq(DPL_SUCCESS,
460                     dpl_dict_add(profile, "profile_name", "viral", 0));
461   /* need this to disable the event log, otherwise the droplet_dir needs to
462    * exist */
463   dpl_assert_int_eq(DPL_SUCCESS, dpl_dict_add(profile, "pricing_dir", "", 0));
464   unsetenv("DPLDIR");
465   unsetenv("DPLPROFILE");
466 
467   dpl_assert_int_eq(nlogged, 0);
468   dpl_set_log_func(log_func);
469 
470   /* create the profile */
471   ctx = dpl_ctx_new_from_dict(profile);
472   dpl_assert_ptr_not_null(ctx);
473   /* no errors so far */
474   dpl_assert_int_eq(nlogged, 0);
475   /* nothing went to stdout */
476   dpl_assert_int_eq(0, file_size(stdout));
477   /* nothing went to stderr */
478   dpl_assert_int_eq(0, file_size(stderr));
479 
480   req = dpl_req_new(ctx);
481   dpl_assert_ptr_not_null(req);
482   req->behavior_flags &= ~DPL_BEHAVIOR_VIRTUAL_HOSTING;
483 
484   /* attempt to connect */
485   s = dpl_try_connect(ctx, req, &conn);
486   dpl_assert_int_eq(DPL_FAILURE, s);
487   dpl_assert_ptr_null(conn);
488   /* at least 1 error logged */
489   dpl_assert_int_ne(nlogged, 0);
490   /* an error mentions the broken address */
491   dpl_assert_int_ne(-1, log_find(host));
492   /* an error mentions the libc error message */
493   dpl_assert_int_ne(-1, log_find(strerror(ECONNREFUSED)));
494   /* nothing went to stdout */
495   dpl_assert_int_eq(0, file_size(stdout));
496   /* nothing went to stderr */
497   dpl_assert_int_eq(0, file_size(stderr));
498 
499   dpl_req_free(req);
500   dpl_ctx_free(ctx);
501   dpl_dict_free(profile);
502   release_bound_port();
503   free(dropdir);
504 }
505 END_TEST
506 
507 
profile_suite()508 Suite* profile_suite()
509 {
510   Suite* s = suite_create("profile");
511   TCase* t = tcase_create("base");
512   tcase_set_timeout(t, 45);
513   tcase_add_checked_fixture(t, setup, teardown);
514   tcase_add_test(t, ctx_new_defaults_test);
515   tcase_add_test(t, ctx_new_dropdir_test);
516   tcase_add_test(t, ctx_new_dropdir_profile_test);
517   tcase_add_test(t, ctx_new_from_dict_test);
518   tcase_add_test(t, ctx_new_params_test);
519   tcase_add_test(t, logging_test);
520   tcase_add_test(t, connect_logging_test);
521   suite_add_tcase(s, t);
522   return s;
523 }
524 
525 #endif /* __linux__ */
526