1 /*
2  * Copyright (c) 2018 by Farsight Security, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <stdio.h>
18 #include <stddef.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 
26 #include "errors.h"
27 
28 #include "nmsg.h"
29 #include "nmsg/asprintf.h"
30 #include "nmsg/alias.h"
31 #include "nmsg/chalias.h"
32 #include "nmsg/container.h"
33 #include "nmsg/msgmod.h"
34 #include "nmsg/vendors.h"
35 #include "nmsg/base/defs.h"
36 #include "defs.h"
37 
38 #define NAME	"test-io"
39 
40 
41 /*
42  * Test a wide variety of nmsg input filter functions.
43  *
44  * A small amount of trickery and indirection is required here.
45  * Certain filters are only applicable to nmsg inputs of type stream.
46  * This precludes certain vehicles like data in json and presentation format.
47  * While we could theoretically just open up a binary nmsg file with the
48  * function nmsg_input_open_sock(), the subsequent read via recvfrom() would
49  * fail on it since it's a local file and not a socket.
50  *
51  * Therefore we create a dummy fd with socketpair() and manually proxy
52  * our locally stored nmsg data across it.
53  */
54 static int
test_io_filters2(void)55 test_io_filters2(void)
56 {
57 	int n;
58 
59 	for (n = 0; n < 11; n++) {
60 		nmsg_input_t i;
61 		nmsg_message_t m;
62 		int fd, sfds[2];
63 
64 		check_return(socketpair(AF_LOCAL, SOCK_STREAM, 0, sfds) != -1);
65 
66 		fd = open(SRCDIR "/tests/generic-tests/newdomain.nmsg", O_RDONLY);
67 		check_return(fd != -1);
68 
69 		i = nmsg_input_open_sock(sfds[0]);
70 		check_return(i != NULL);
71 
72 		/* Only need to try this once. */
73 		if (!n) {
74 			check(nmsg_input_set_filter_msgtype_byname(i, "some_vendor", "nonexistent_type") != nmsg_res_success);
75 		}
76 
77 		/* The ordering is particular. Every odd numbered test should
78 		 * succeed, and vice versa. */
79 		switch(n) {
80 			case 1:
81 				nmsg_input_set_filter_msgtype(i, NMSG_VENDOR_SIE_ID, NMSG_VENDOR_SIE_DNSDEDUPE_ID);
82 				break;
83 			case 2:
84 				nmsg_input_set_filter_msgtype(i, NMSG_VENDOR_SIE_ID, NMSG_VENDOR_SIE_NEWDOMAIN_ID);
85 				break;
86 			case 3:
87 				nmsg_input_set_filter_group(i, 2835122346);
88 				break;
89 			case 4:
90 				nmsg_input_set_filter_group(i, 0);
91 				break;
92 			case 5:
93 				nmsg_input_set_filter_source(i, 1235817825);
94 				break;
95 			case 6:
96 				nmsg_input_set_filter_source(i, 0xa1ba02cf);
97 				break;
98 			case 7:
99 				nmsg_input_set_filter_operator(i, 138158152);
100 				break;
101 			case 8:
102 				nmsg_input_set_filter_operator(i, 0);
103 				break;
104 			case 9:
105 				check(nmsg_input_set_filter_msgtype_byname(i, "SIE", "dnsdedupe") == nmsg_res_success);
106 				break;
107 			case 10:
108 				check(nmsg_input_set_filter_msgtype_byname(i, "SIE", "newdomain") == nmsg_res_success);
109 				break;
110 			default:
111 				break;
112 		}
113 
114 		while (1) {
115 			char buf[1024];
116 			int nread;
117 
118 			nread = read(fd, buf, sizeof(buf));
119 
120 			if (nread <= 0)
121 				break;
122 
123 			check_return(write(sfds[1], buf, nread) == nread);
124 		}
125 
126 		if (!(n % 2)) {
127 			check(nmsg_input_read(i, &m) == nmsg_res_success);
128 		} else {
129 			check(nmsg_input_read(i, &m) != nmsg_res_success);
130 		}
131 
132 		check(nmsg_input_close(&i) == nmsg_res_success);
133 
134 		close(fd);
135 		close(sfds[1]);
136 	}
137 
138 	l_return_test_status();
139 }
140 
141 static void *user_data = (void *)0xdeadbeef;
142 static int touched_exit, touched_atstart, touched_close, num_received, touched_filter;
143 
144 static void
test_close_fp(struct nmsg_io_close_event * ce)145 test_close_fp(struct nmsg_io_close_event *ce)
146 {
147 	(void)ce; /* unused parameter */
148 
149 	__sync_add_and_fetch(&touched_close, 1);
150 
151 	return;
152 }
153 
154 static void
test_atstart_fp(unsigned threadno,void * user)155 test_atstart_fp(unsigned threadno, void *user)
156 {
157 	(void)threadno; /* unused parameter */
158 
159 	if (user == user_data)
160 		__sync_add_and_fetch(&touched_atstart, 1);
161 
162 	return;
163 }
164 
165 static void
test_atexit_fp(unsigned threadno,void * user)166 test_atexit_fp(unsigned threadno, void *user)
167 {
168 	(void)threadno; /* unused parameter */
169 
170 	if (user == user_data)
171 		__sync_add_and_fetch(&touched_exit, 1);
172 
173 	return;
174 }
175 
176 static void
output_callback(nmsg_message_t msg,void * user)177 output_callback(nmsg_message_t msg, void *user)
178 {
179 	(void)msg; /* unused parameter */
180 
181 	if (user == user_data)
182 		__sync_add_and_fetch(&num_received, 1);
183 
184 	return;
185 }
186 
187 /* A filter to permit only msg type NMSG_VENDOR_SIE_DNSDEDUPE_ID */
188 static nmsg_res
filter_callback(nmsg_message_t * msg,void * user,nmsg_filter_message_verdict * vres)189 filter_callback(nmsg_message_t *msg, void *user, nmsg_filter_message_verdict *vres)
190 {
191 
192 	if (user != user_data)
193 		return nmsg_res_failure;
194 
195 	if (nmsg_message_get_msgtype(*msg) == NMSG_VENDOR_SIE_DNSDEDUPE_ID)
196 		*vres = nmsg_filter_message_verdict_DROP;
197 	else
198 		*vres = nmsg_filter_message_verdict_ACCEPT;
199 
200 	__sync_add_and_fetch(&touched_filter, 1);
201 
202 	return nmsg_res_success;
203 }
204 
205 /* Just to test the filter policy. */
206 static nmsg_res
filter_callback2(nmsg_message_t * msg,void * user,nmsg_filter_message_verdict * vres)207 filter_callback2(nmsg_message_t *msg, void *user, nmsg_filter_message_verdict *vres)
208 {
209 	(void)msg; /* unused parameter */
210 
211 	if (user != user_data)
212 		return nmsg_res_failure;
213 
214 	*vres = nmsg_filter_message_verdict_DECLINED;
215 	__sync_add_and_fetch(&touched_filter, 1);
216 
217 	return nmsg_res_success;
218 }
219 
220 
221 /* XXX: Partially crippled.
222  * Test custom nmsg io filter callbacks and output callbacks;
223  * These are for close, at-start, and at-exit.
224  * Test nmsg_io_set_count() [broken]. */
225 static int
test_io_filters(void)226 test_io_filters(void)
227 {
228 	nmsg_io_t io;
229 	nmsg_output_t o;
230 	size_t run_cnt = 0;
231 
232 	/*
233 	 * Loop #1: Verify all 10 nmsgs read normally.
234 	 * Loop #2: Set count to 7 and verify 7 msgs read normally.
235 	 * Loop #3: Apply first filter callback. It should drop all msgs of type !=
236 	 *          dnsdedupe, meaning that half (5) of the packets will be dropped.
237 	 * Loop #4: Apply second filter callback.
238 	 * Loop #5: Apply second filter callback with default filter policy of DROP.
239 	 */
240 	while (run_cnt < 5) {
241 		io = nmsg_io_init();
242 		check_return(io != NULL);
243 
244 		/* Feed the nmsg io loop with 2 nmsg files that have 5 messages each. */
245 		check_return(nmsg_io_add_input_fname(io, SRCDIR "/tests/generic-tests/dedupe.nmsg", NULL) == nmsg_res_success);
246 		check_return(nmsg_io_add_input_fname(io, SRCDIR "/tests/generic-tests/newdomain.nmsg", NULL) == nmsg_res_success);
247 
248 		/* Use an output callback for the output. */
249 		o = nmsg_output_open_callback(output_callback, user_data);
250 		check_return(o != NULL);
251 		check_return(nmsg_io_add_output(io, o, user_data) == nmsg_res_success);
252 
253 		/* Reset the counters and set up all custom callbacks. */
254 		touched_atstart = touched_exit = touched_close = num_received = touched_filter = 0;
255 		nmsg_io_set_close_fp(io, test_close_fp);
256 		nmsg_io_set_atstart_fp(io, test_atstart_fp, user_data);
257 		nmsg_io_set_atexit_fp(io, test_atexit_fp, user_data);
258 
259 		if (!run_cnt)
260 			nmsg_io_set_count(io, 10);
261 		else if (run_cnt == 1)
262 			nmsg_io_set_count(io, 7);
263 		else
264 			nmsg_io_set_count(io, 10);
265 
266 		if (run_cnt == 2) {
267 			check(nmsg_io_add_filter(io, filter_callback, user_data) == nmsg_res_success);
268 		} else if (run_cnt == 3) {
269 			check(nmsg_io_add_filter(io, filter_callback2, user_data) == nmsg_res_success);
270 		} else if (run_cnt == 4) {
271 			check(nmsg_io_add_filter(io, filter_callback2, user_data) == nmsg_res_success);
272 			/* XXX: This isn't working; it appears to be a bug in libnmsg? */
273 			nmsg_io_set_filter_policy(io, nmsg_filter_message_verdict_DROP);
274 
275 		}
276 
277 		check(nmsg_io_loop(io) == nmsg_res_success);
278 
279 		nmsg_io_destroy(&io);
280 		check(io == NULL);
281 
282 		check(touched_atstart != 0);
283 		check(touched_exit == touched_atstart);
284 		check(touched_close >= touched_atstart);
285 
286 		if (run_cnt == 2) {
287 			check(touched_filter == 10);
288 			check(num_received == 5);
289 		} else if (run_cnt == 3) {
290 			check(touched_filter == 10);
291 			check(num_received == 10);
292 		} else if (run_cnt == 4) {
293 			check(touched_filter == 10);
294 			check(num_received == 0);
295 		} else {
296 			check(touched_filter == 0);
297 			check(num_received == 10);
298 		}
299 
300 		run_cnt++;
301 	}
302 
303 	l_return_test_status();
304 }
305 
306 int
main(void)307 main(void)
308 {
309 	check_abort(nmsg_init() == nmsg_res_success);
310 
311 	check_explicit2_display_only(test_io_filters() == 0, "test-io/ test_io_filters");
312 	check_explicit2_display_only(test_io_filters2() == 0, "test-io/ test_io_filters2");
313 
314         g_check_test_status(false);
315 
316 }
317