1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "test-lib.h"
5 #include "mempool.h"
6 #include "buffer.h"
7 #include "str.h"
8 #include "array.h"
9 #include "istream.h"
10 #include "ostream.h"
11 #include "istream-dot.h"
12 #include "ostream-dot.h"
13 #include "net.h"
14 #include "iostream-temp.h"
15 #include "program-client.h"
16
17 #include <unistd.h>
18
19 static const char *pclient_test_io_string =
20 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r\n"
21 "Praesent vehicula ac leo vel placerat. Nullam placerat \r\n"
22 "volutpat leo, sed ultricies felis pulvinar quis. Nam \r\n"
23 "tempus, augue ut tempor cursus, neque felis commodo lacus, \r\n"
24 "sit amet tincidunt arcu justo vel augue. Proin dapibus \r\n"
25 "vulputate maximus. Mauris congue lacus felis, sed varius \r\n"
26 "leo finibus sagittis. Cum sociis natoque penatibus et magnis \r\n"
27 "dis parturient montes, nascetur ridiculus mus. Aliquam \r\n"
28 "laoreet arcu a hendrerit consequat. Duis vitae erat tellus.";
29
30 static struct program_client_settings pc_set = {
31 .client_connect_timeout_msecs = 5000,
32 .input_idle_timeout_msecs = 10000,
33 .debug = FALSE,
34 };
35
36 static struct test_server {
37 struct ioloop *ioloop;
38 struct io *io;
39 struct timeout *to;
40 struct test_client *client;
41 int listen_fd;
42 in_port_t port;
43 unsigned int io_loop_ref;
44 } test_globals;
45
46 struct test_client {
47 pool_t pool;
48 int fd;
49 struct io *io;
50 struct istream *in;
51 struct ostream *out;
52 struct ostream *os_body;
53 struct istream *is_body;
54 struct istream *body;
55 ARRAY_TYPE(const_string) args;
56 enum {
57 CLIENT_STATE_INIT,
58 CLIENT_STATE_VERSION,
59 CLIENT_STATE_ARGS,
60 CLIENT_STATE_BODY,
61 CLIENT_STATE_FINISH
62 } state;
63 };
64
test_program_io_loop_run(void)65 static void test_program_io_loop_run(void)
66 {
67 if (test_globals.io_loop_ref++ == 0)
68 io_loop_run(current_ioloop);
69 }
70
test_program_io_loop_stop(void)71 static void test_program_io_loop_stop(void)
72 {
73 if (--test_globals.io_loop_ref == 0)
74 io_loop_stop(current_ioloop);
75 }
76
test_program_client_destroy(struct test_client ** _client)77 static void test_program_client_destroy(struct test_client **_client)
78 {
79 struct test_client *client = *_client;
80 *_client = NULL;
81
82 if (o_stream_finish(client->out) < 0)
83 i_error("output error: %s", o_stream_get_error(client->out));
84
85 io_remove(&client->io);
86 o_stream_unref(&client->out);
87 i_stream_unref(&client->in);
88 o_stream_unref(&client->os_body);
89 i_stream_unref(&client->is_body);
90 i_stream_unref(&client->body);
91 i_close_fd(&client->fd);
92 pool_unref(&client->pool);
93 test_globals.client = NULL;
94 test_program_io_loop_stop();
95 }
96
97 static int
test_program_input_handle(struct test_client * client,const char * line)98 test_program_input_handle(struct test_client *client, const char *line)
99 {
100 int cmp = -1;
101 const char *arg;
102
103 switch(client->state) {
104 case CLIENT_STATE_INIT:
105 cmp = strncmp(line, "VERSION\tscript\t", 15);
106 test_assert(cmp == 0);
107 if (cmp == 0)
108 client->state = CLIENT_STATE_VERSION;
109 else
110 return -1;
111 break;
112 case CLIENT_STATE_VERSION:
113 if (strcmp(line, "noreply") == 0 ||
114 strcmp(line, "-") == 0)
115 cmp = 0;
116 test_assert(cmp == 0);
117 if (cmp == 0)
118 client->state = CLIENT_STATE_ARGS;
119 else
120 return -1;
121 break;
122 case CLIENT_STATE_ARGS:
123 if (strcmp(line, "") == 0) {
124 array_append_zero(&client->args);
125 client->state = CLIENT_STATE_BODY;
126 return 0;
127 }
128 arg = p_strdup(client->pool, line);
129 array_push_back(&client->args, &arg);
130 break;
131 case CLIENT_STATE_BODY:
132 if (client->os_body == NULL) {
133 client->os_body = iostream_temp_create_named(
134 ".dovecot.test.", 0, "test_program_input body");
135 }
136 if (client->is_body == NULL)
137 client->is_body = i_stream_create_dot(client->in, FALSE);
138 switch (o_stream_send_istream(client->os_body,
139 client->is_body)) {
140 case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
141 i_panic("Cannot write to ostream-temp: %s",
142 o_stream_get_error(client->os_body));
143 case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
144 i_warning("Client stream error: %s",
145 i_stream_get_error(client->is_body));
146 return -1;
147 case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
148 break;
149 case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
150 client->body = iostream_temp_finish(&client->os_body,
151 SIZE_MAX);
152 i_stream_unref(&client->is_body);
153 client->state = CLIENT_STATE_FINISH;
154 return 0;
155 case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
156 i_panic("Cannot write to ostream-temp");
157 }
158 break;
159 case CLIENT_STATE_FINISH:
160 if (i_stream_read_eof(client->in))
161 return 1;
162 break;
163 }
164 return 0;
165 }
166
test_program_end(struct test_client * client)167 static void test_program_end(struct test_client *client)
168 {
169 timeout_remove(&test_globals.to);
170 test_program_client_destroy(&client);
171 }
172
test_program_run(struct test_client * client)173 static void test_program_run(struct test_client *client)
174 {
175 const char *const *args;
176 bool disconnect_later = FALSE;
177 unsigned int count;
178
179 struct ostream *os;
180
181 timeout_remove(&test_globals.to);
182 test_assert(array_is_created(&client->args));
183 if (array_is_created(&client->args)) {
184 args = array_get(&client->args, &count);
185 test_assert(count > 0);
186 if (count >= 2) {
187 if (strcmp(args[0], "test_program_success") == 0) {
188 /* Return hello world */
189 i_assert(count >= 3);
190 o_stream_nsend_str(client->out,
191 t_strdup_printf("%s %s\r\n.\n+\n",
192 args[1], args[2]));
193 } else if (strcmp(args[0], "test_program_io") == 0) {
194 os = o_stream_create_dot(client->out, FALSE);
195 o_stream_nsend_istream(os, client->body);
196 test_assert(o_stream_finish(os) > 0);
197 o_stream_unref(&os);
198 o_stream_nsend_str(client->out, "+\n");
199 } else if (strcmp(args[0],
200 "test_program_failure") == 0) {
201 o_stream_nsend_str(client->out, ".\n-\n");
202 }
203 } else
204 o_stream_nsend_str(client->out, ".\n-\n");
205 if (count >= 3 && strcmp(args[1], "slow_disconnect") == 0)
206 disconnect_later = TRUE;
207 }
208
209 test_assert(o_stream_flush(client->out) > 0);
210
211 if (!disconnect_later)
212 test_program_client_destroy(&client);
213 else {
214 test_globals.to = timeout_add_short(
215 500, test_program_end, client);
216 }
217 }
218
test_program_input(struct test_client * client)219 static void test_program_input(struct test_client *client)
220 {
221 const char *line = "";
222 int ret = 0;
223
224 while (ret >= 0) {
225 if (client->state >= CLIENT_STATE_BODY) {
226 ret = test_program_input_handle(client, NULL);
227 break;
228 }
229 while (client->state < CLIENT_STATE_BODY) {
230 line = i_stream_read_next_line(client->in);
231 if (line == NULL) {
232 ret = 0;
233 break;
234 }
235 ret = test_program_input_handle(client, line);
236 if (ret < 0) {
237 i_warning("Client sent invalid line: %s", line);
238 break;
239 }
240 }
241 }
242
243 if (ret < 0 || client->in->stream_errno != 0) {
244 test_program_client_destroy(&client);
245 return;
246 }
247 if (!client->in->eof)
248 return;
249
250 if (client->state < CLIENT_STATE_FINISH)
251 i_warning("Client prematurely disconnected");
252
253 io_remove(&client->io);
254 /* Incur slight delay to check if the connection gets prematurely
255 closed. */
256 test_globals.to = timeout_add_short(100, test_program_run, client);
257 }
258
test_program_connected(struct test_server * server)259 static void test_program_connected(struct test_server *server)
260 {
261 struct test_client *client;
262 int fd;
263
264 i_assert(server->client == NULL);
265 fd = net_accept(server->listen_fd, NULL, NULL); /* makes no sense on net */
266 if (fd < 0)
267 i_fatal("Failed to accept connection: %m");
268
269 net_set_nonblock(fd, TRUE);
270
271 pool_t pool = pool_alloconly_create("test_program client", 1024);
272 client = p_new(pool, struct test_client, 1);
273 client->pool = pool;
274 client->fd = fd;
275 client->in = i_stream_create_fd(fd, SIZE_MAX);
276 client->out = o_stream_create_fd(fd, SIZE_MAX);
277 client->io = io_add_istream(client->in, test_program_input, client);
278 p_array_init(&client->args, client->pool, 2);
279 server->client = client;
280
281 test_program_io_loop_run();
282 }
283
test_program_setup(void)284 static void test_program_setup(void)
285 {
286 struct ip_addr ip;
287
288 test_begin("test_program_setup");
289
290 test_globals.ioloop = io_loop_create();
291 io_loop_set_current(test_globals.ioloop);
292
293 /* Create listener */
294 test_globals.port = 0;
295 test_assert(net_addr2ip("127.0.0.1", &ip) == 0);
296
297 test_globals.listen_fd = net_listen(&ip, &test_globals.port, 1);
298
299 if (test_globals.listen_fd < 0)
300 i_fatal("Cannot create TCP listener: %m");
301
302 test_globals.io = io_add(test_globals.listen_fd, IO_READ,
303 test_program_connected, &test_globals);
304 test_end();
305 }
306
test_program_teardown(void)307 static void test_program_teardown(void)
308 {
309 test_begin("test_program_teardown");
310
311 if (test_globals.client != NULL)
312 test_program_client_destroy(&test_globals.client);
313 io_remove(&test_globals.io);
314 i_close_fd(&test_globals.listen_fd);
315 io_loop_destroy(&test_globals.ioloop);
316 test_end();
317 }
318
test_program_async_callback(enum program_client_exit_status result,int * ret)319 static void test_program_async_callback(enum program_client_exit_status result,
320 int *ret)
321 {
322 *ret = (int)result;
323 test_program_io_loop_stop();
324 }
325
test_program_success(void)326 static void test_program_success(void)
327 {
328 struct program_client *pc;
329 int ret = -2;
330
331 const char *const args[] = {
332 "test_program_success", "hello", "world", NULL
333 };
334
335 test_begin("test_program_success");
336
337 pc = program_client_net_create("127.0.0.1", test_globals.port, args,
338 &pc_set, FALSE);
339
340 buffer_t *output = buffer_create_dynamic(default_pool, 16);
341 struct ostream *os = test_ostream_create(output);
342 program_client_set_output(pc, os);
343
344 program_client_run_async(pc, test_program_async_callback, &ret);
345
346 if (ret == -2)
347 test_program_io_loop_run();
348
349 test_assert(ret == 1);
350 test_assert(strcmp(str_c(output), "hello world") == 0);
351
352 program_client_destroy(&pc);
353
354 o_stream_unref(&os);
355 buffer_free(&output);
356
357 i_assert(test_globals.client == NULL);
358
359 test_end();
360 }
361
test_program_io_common(const char * const * args)362 static void test_program_io_common(const char *const *args)
363 {
364 struct program_client *pc;
365 int ret = -2;
366
367 pc = program_client_net_create("127.0.0.1", test_globals.port, args,
368 &pc_set, FALSE);
369
370 struct istream *is = test_istream_create(pclient_test_io_string);
371 program_client_set_input(pc, is);
372
373 buffer_t *output = buffer_create_dynamic(default_pool, 16);
374 struct ostream *os = test_ostream_create(output);
375 program_client_set_output(pc, os);
376
377 program_client_run_async(pc, test_program_async_callback, &ret);
378
379 if (ret == -2)
380 test_program_io_loop_run();
381
382 test_assert(ret == 1);
383 test_assert(strcmp(str_c(output), pclient_test_io_string) == 0);
384
385 program_client_destroy(&pc);
386
387 i_stream_unref(&is);
388 o_stream_unref(&os);
389 buffer_free(&output);
390
391 i_assert(test_globals.client == NULL);
392 }
393
test_program_io(void)394 static void test_program_io(void)
395 {
396 const char *args[3] = {
397 "test_program_io", NULL, NULL
398 };
399
400 test_begin("test_program_io (async)");
401
402 test_program_io_common(args);
403
404 test_end();
405
406 args[1] = "slow_disconnect";
407
408 test_begin("test_program_io (async, slow disconnect)");
409
410 test_program_io_common(args);
411
412 test_end();
413 }
414
test_program_failure(void)415 static void test_program_failure(void)
416 {
417 struct program_client *pc;
418 int ret = -2;
419
420 const char *const args[] = {
421 "test_program_failure", NULL
422 };
423
424 test_begin("test_program_failure");
425
426 pc = program_client_net_create("127.0.0.1", test_globals.port, args,
427 &pc_set, FALSE);
428
429 buffer_t *output = buffer_create_dynamic(default_pool, 16);
430 struct ostream *os = test_ostream_create(output);
431 program_client_set_output(pc, os);
432
433 program_client_run_async(pc, test_program_async_callback, &ret);
434
435 if (ret == -2)
436 test_program_io_loop_run();
437
438 test_assert(ret == 0);
439
440 program_client_destroy(&pc);
441
442 o_stream_unref(&os);
443 buffer_free(&output);
444
445 i_assert(test_globals.client == NULL);
446
447 test_end();
448 }
449
test_program_noreply(void)450 static void test_program_noreply(void)
451 {
452 struct program_client *pc;
453 int ret = -2;
454
455 const char *const args[] = {
456 "test_program_success", "hello", "world", NULL
457 };
458
459 test_begin("test_program_noreply");
460
461 pc = program_client_net_create("127.0.0.1", test_globals.port, args,
462 &pc_set, TRUE);
463
464 program_client_run_async(pc, test_program_async_callback, &ret);
465
466 if (ret == -2)
467 test_program_io_loop_run();
468
469 test_assert(ret == 1);
470
471 program_client_destroy(&pc);
472
473 i_assert(test_globals.client == NULL);
474
475 test_end();
476 }
477
test_program_refused(void)478 static void test_program_refused(void)
479 {
480 struct program_client *pc;
481 struct ip_addr ips[4];
482 int ret = -2;
483
484 const char *const args[] = {
485 "test_program_success", "hello", "world", NULL
486 };
487
488 test_begin("test_program_refused");
489
490 if (net_addr2ip("::1", &ips[0]) < 0 ||
491 net_addr2ip("127.0.0.3", &ips[1]) < 0 ||
492 net_addr2ip("127.0.0.2", &ips[2]) < 0 ||
493 net_addr2ip("127.0.0.1", &ips[3]) < 0) {
494 i_fatal("Cannot convert addresses");
495 }
496
497 pc = program_client_net_create_ips(ips, N_ELEMENTS(ips),
498 test_globals.port, args,
499 &pc_set, TRUE);
500
501 test_expect_errors(N_ELEMENTS(ips)-1);
502 program_client_run_async(pc, test_program_async_callback, &ret);
503
504 if (ret == -2)
505 test_program_io_loop_run();
506
507 test_assert(ret == 1);
508
509 program_client_destroy(&pc);
510
511 test_end();
512 }
513
main(int argc,char * argv[])514 int main(int argc, char *argv[])
515 {
516 int ret, c;
517
518 void (*tests[])(void) = {
519 test_program_setup,
520 test_program_success,
521 test_program_io,
522 test_program_failure,
523 test_program_noreply,
524 test_program_refused,
525 test_program_teardown,
526 NULL
527 };
528
529 lib_init();
530
531 while ((c = getopt(argc, argv, "D")) > 0) {
532 switch (c) {
533 case 'D':
534 pc_set.debug = TRUE;
535 break;
536 default:
537 i_fatal("Usage: %s [-D]", argv[0]);
538 }
539 }
540
541 ret = test_run(tests);
542
543 lib_deinit();
544 return ret;
545 }
546