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