1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright 2020 Alex Richardson <arichardson@FreeBSD.org>
5 *
6 * This software was developed by SRI International and the University of
7 * Cambridge Computer Laboratory (Department of Computer Science and
8 * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
9 * DARPA SSITH research programme.
10 *
11 * This work was supported by Innovate UK project 105694, "Digital Security by
12 * Design (DSbD) Technology Platform Prototype".
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are met:
16 * 1. Redistributions of source code must retain the above copyright notice,
17 *    this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright notice,
19 *    this list of conditions and the following disclaimer in the documentation
20 *    and/or other materials provided with the distribution.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
23 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
26 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <sys/types.h>
37#include <sys/param.h>
38#include <err.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <stdbool.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <spawn.h>
45#include <sys/module.h>
46#include <sys/sbuf.h>
47#include <sys/stat.h>
48#include <sys/wait.h>
49
50#include <atf-c.h>
51
52/*
53 * Tests 0001-0999 are copied from OpenBSD's regress/sbin/pfctl.
54 * Tests 1001-1999 are ours (FreeBSD's own).
55 *
56 * pf: Run pfctl -nv on pfNNNN.in and check that the output matches pfNNNN.ok.
57 *     Copied from OpenBSD.  Main differences are some things not working
58 *     in FreeBSD:
59 *         * The action 'match'
60 *         * The command 'set reassemble'
61 *         * The 'from'/'to' options together with 'route-to'
62 *         * The option 'scrub' (it is an action in FreeBSD)
63 *         * Accepting undefined routing tables in actions (??: see pf0093.in)
64 *         * The 'route' option
65 *         * The 'set queue def' option
66 * selfpf: Feed pfctl output through pfctl again and verify it stays the same.
67 *         Copied from OpenBSD.
68 */
69
70static bool
71check_pf_module_available(void)
72{
73	int modid;
74	struct module_stat stat;
75
76	if ((modid = modfind("pf")) < 0) {
77		warn("pf module not found");
78		return false;
79	}
80	stat.version = sizeof(struct module_stat);
81	if (modstat(modid, &stat) < 0) {
82		warn("can't stat pf module id %d", modid);
83		return false;
84	}
85	return (true);
86}
87
88extern char **environ;
89
90static struct sbuf *
91read_fd(int fd, size_t sizehint)
92{
93	struct sbuf *sb;
94	ssize_t count;
95	char buffer[MAXBSIZE];
96
97	sb = sbuf_new(NULL, NULL, sizehint, SBUF_AUTOEXTEND);
98	errno = 0;
99	while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
100		sbuf_bcat(sb, buffer, count);
101	}
102	ATF_REQUIRE_ERRNO(0, count == 0 && "Should have reached EOF");
103	sbuf_finish(sb); /* Ensure NULL-termination */
104	return (sb);
105}
106
107static struct sbuf *
108read_file(const char *filename)
109{
110	struct stat s;
111	struct sbuf *result;
112	int fd;
113
114	errno = 0;
115	ATF_REQUIRE_EQ_MSG(stat(filename, &s), 0, "cannot stat %s", filename);
116	fd = open(filename, O_RDONLY);
117	ATF_REQUIRE_ERRNO(0, fd > 0);
118	result = read_fd(fd, s.st_size);
119	ATF_REQUIRE_ERRNO(0, close(fd) == 0);
120	return (result);
121}
122
123static void
124run_pfctl_test(const char *input_path, const char *expected_path,
125    const atf_tc_t *tc)
126{
127	int status;
128	pid_t pid;
129	int pipefds[2];
130	char input_files_path[PATH_MAX];
131	struct sbuf *expected_output;
132	struct sbuf *real_output;
133	posix_spawn_file_actions_t action;
134
135	if (!check_pf_module_available())
136		atf_tc_skip("pf(4) is not loaded");
137
138	/* The test inputs need to be able to use relative includes. */
139	snprintf(input_files_path, sizeof(input_files_path), "%s/files",
140	    atf_tc_get_config_var(tc, "srcdir"));
141	ATF_REQUIRE_ERRNO(0, chdir(input_files_path) == 0);
142
143	ATF_REQUIRE_ERRNO(0, pipe(pipefds) == 0);
144	expected_output = read_file(expected_path);
145
146	posix_spawn_file_actions_init(&action);
147	posix_spawn_file_actions_addclose(&action, STDIN_FILENO);
148	posix_spawn_file_actions_addclose(&action, pipefds[1]);
149	posix_spawn_file_actions_adddup2(&action, pipefds[0], STDOUT_FILENO);
150	posix_spawn_file_actions_adddup2(&action, pipefds[0], STDERR_FILENO);
151
152	const char *argv[] = { "pfctl", "-o", "none", "-nvf", input_path,
153		NULL };
154	printf("Running %s %s %s %s %s\n", argv[0], argv[1], argv[2], argv[3],
155	    argv[4]);
156	status = posix_spawnp(
157	    &pid, "pfctl", &action, NULL, __DECONST(char **, argv), environ);
158	ATF_REQUIRE_EQ_MSG(
159	    status, 0, "posix_spawn failed: %s", strerror(errno));
160	posix_spawn_file_actions_destroy(&action);
161	close(pipefds[0]);
162
163	real_output = read_fd(pipefds[1], 0);
164	printf("---\n%s---\n", sbuf_data(real_output));
165	ATF_REQUIRE_EQ(waitpid(pid, &status, 0), pid);
166	ATF_REQUIRE_MSG(WIFEXITED(status),
167	    "pfctl returned non-zero! Output:\n %s", sbuf_data(real_output));
168
169	ATF_CHECK_STREQ(sbuf_data(expected_output), sbuf_data(real_output));
170	sbuf_delete(expected_output);
171	sbuf_delete(real_output);
172	close(pipefds[1]);
173}
174
175static void
176do_pf_test(const char *number, const atf_tc_t *tc)
177{
178	char *input_path;
179	char *expected_path;
180	asprintf(&input_path, "%s/files/pf%s.in",
181	    atf_tc_get_config_var(tc, "srcdir"), number);
182	asprintf(&expected_path, "%s/files/pf%s.ok",
183	    atf_tc_get_config_var(tc, "srcdir"), number);
184	run_pfctl_test(input_path, expected_path, tc);
185	free(input_path);
186	free(expected_path);
187}
188
189static void
190do_selfpf_test(const char *number, const atf_tc_t *tc)
191{
192	char *expected_path;
193	asprintf(&expected_path, "%s/files/pf%s.ok",
194	    atf_tc_get_config_var(tc, "srcdir"), number);
195	run_pfctl_test(expected_path, expected_path, tc);
196	free(expected_path);
197}
198
199#define PFCTL_TEST(number, descr)				\
200	ATF_TC(pf##number);					\
201	ATF_TC_HEAD(pf##number, tc)				\
202	{							\
203		atf_tc_set_md_var(tc, "descr", descr);		\
204	}							\
205	ATF_TC_BODY(pf##number, tc)				\
206	{							\
207		do_pf_test(#number, tc);			\
208	}							\
209	ATF_TC(selfpf##number);					\
210	ATF_TC_HEAD(selfpf##number, tc)				\
211	{							\
212		atf_tc_set_md_var(tc, "descr", "Self " descr);	\
213	}							\
214	ATF_TC_BODY(selfpf##number, tc)				\
215	{							\
216		do_selfpf_test(#number, tc);			\
217	}
218#include "pfctl_test_list.inc"
219#undef PFCTL_TEST
220
221ATF_TP_ADD_TCS(tp)
222{
223#define PFCTL_TEST(number, descr)		\
224	ATF_TP_ADD_TC(tp, pf##number);		\
225	ATF_TP_ADD_TC(tp, selfpf##number);
226#include "pfctl_test_list.inc"
227#undef PFCTL_TEST
228
229	return atf_no_error();
230}
231