1 /*
2  * Copyright (c) 2004 Camiel Dobbelaar, <cd@sentia.nl>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <sys/ioctl.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 
21 #include <net/if.h>
22 #include <net/pf/pfvar.h>
23 #include <netinet/in.h>
24 #include <netinet/tcp.h>
25 #include <arpa/inet.h>
26 
27 #include <err.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include "filter.h"
36 
37 static struct pfioc_pooladdr	pfp;
38 static struct pfioc_rule	pfr;
39 static struct pfioc_trans	pft;
40 static struct pfioc_trans_e	pfte;
41 static int dev;
42 
43 void
filter_init(char * qname,char * tagname)44 filter_init(char *qname, char *tagname)
45 {
46 	struct pf_status status;
47 
48 	dev = open("/dev/pf", O_RDWR);
49 	if (dev == -1)
50 		err(1, "/dev/pf");
51 	if (ioctl(dev, DIOCGETSTATUS, &status) == -1)
52 		err(1, "DIOCGETSTATUS");
53 	if (!status.running)
54 		errx(1, "pf is disabled");
55 
56 	/*
57 	 * Initialize the structs for filter_allow.
58 	 */
59 
60 	memset(&pfp, 0, sizeof pfp);
61 	memset(&pfr, 0, sizeof pfr);
62 	memset(&pft, 0, sizeof pft);
63 	memset(&pfte, 0, sizeof pfte);
64 
65 	pft.size = 1;
66 	pft.esize = sizeof pfte;
67 	pft.array = &pfte;
68 	pfte.rs_num = PF_RULESET_FILTER;
69 
70 	/*
71 	 * pass [quick] log inet proto tcp \
72 	 *     from $src/32 to $dst/32 port = $d_port flags S/SAFR keep state
73 	 *     [tag tagname] [queue qname]
74 	 */
75 	pfr.rule.action = PF_PASS;
76 	if (tagname == NULL)
77 		pfr.rule.quick = 1;
78 	pfr.rule.log = 1;
79 	pfr.rule.af = AF_INET;
80 	pfr.rule.proto = IPPROTO_TCP;
81 	pfr.rule.src.addr.type = PF_ADDR_ADDRMASK;
82 	memset(&pfr.rule.src.addr.v.a.mask.v4, 255, 4);
83 	pfr.rule.dst.addr.type = PF_ADDR_ADDRMASK;
84 	memset(&pfr.rule.dst.addr.v.a.mask.v4, 255, 4);
85 	pfr.rule.dst.port_op = PF_OP_EQ;
86 	pfr.rule.keep_state = 1;
87 	pfr.rule.flags = TH_SYN;
88 	pfr.rule.flagset = (TH_SYN|TH_ACK|TH_FIN|TH_RST);
89 	if (tagname != NULL)
90 		strlcpy(pfr.rule.tagname, tagname, sizeof pfr.rule.tagname);
91 	if (qname != NULL)
92 		strlcpy(pfr.rule.qname, qname, sizeof pfr.rule.qname);
93 }
94 
95 int
filter_allow(u_int32_t id,struct in_addr * src,struct in_addr * src2,struct in_addr * dst,u_int16_t d_port)96 filter_allow(u_int32_t id, struct in_addr *src, struct in_addr *src2,
97     struct in_addr *dst, u_int16_t d_port)
98 {
99 	char an[PF_ANCHOR_NAME_SIZE];
100 
101 	/* The structs are initialized in filter_init. */
102 
103 	snprintf(an, PF_ANCHOR_NAME_SIZE, "%s/%d.%d", FTPSESAME_ANCHOR,
104 	    getpid(), id);
105 	strlcpy(pfp.anchor, an, PF_ANCHOR_NAME_SIZE);
106 	strlcpy(pfr.anchor, an, PF_ANCHOR_NAME_SIZE);
107 	strlcpy(pfte.anchor, an, PF_ANCHOR_NAME_SIZE);
108 
109 	if (ioctl(dev, DIOCXBEGIN, &pft) == -1)
110 		return (0);
111 	pfr.ticket = pfte.ticket;
112 
113 	if (ioctl(dev, DIOCBEGINADDRS, &pfp) == -1)
114 		return (0);
115 	pfr.pool_ticket = pfp.ticket;
116 
117 	if (src != NULL && dst != NULL && d_port != 0) {
118 		memcpy(&pfr.rule.src.addr.v.a.addr.v4, src, 4);
119 		memcpy(&pfr.rule.dst.addr.v.a.addr.v4, dst, 4);
120 		pfr.rule.dst.port[0] = htons(d_port);
121 		if (ioctl(dev, DIOCADDRULE, &pfr) == -1)
122 			return (0);
123 
124 		if (src2 != NULL) {
125 			memcpy(&pfr.rule.src.addr.v.a.addr.v4, src2, 4);
126 			if (ioctl(dev, DIOCADDRULE, &pfr) == -1)
127 				return (0);
128 		}
129 	}
130 
131 	if (ioctl(dev, DIOCXCOMMIT, &pft) == -1)
132 		return (0);
133 
134 	return (1);
135 }
136 
137 int
filter_remove(u_int32_t id)138 filter_remove(u_int32_t id)
139 {
140 	return (filter_allow(id, NULL, NULL, NULL, 0));
141 }
142 
143 int
filter_lookup(struct in_addr * src,struct in_addr * dst,u_int16_t s_port,u_int16_t d_port,struct in_addr * src_real)144 filter_lookup(struct in_addr *src, struct in_addr *dst, u_int16_t s_port,
145     u_int16_t d_port, struct in_addr *src_real)
146 {
147 	struct pfioc_natlook pnl;
148 	int r;
149 
150 	memset(&pnl, 0, sizeof pnl);
151 	pnl.af = AF_INET;
152 	memcpy(&pnl.saddr.v4, src, sizeof pnl.saddr.v4);
153 	memcpy(&pnl.daddr.v4, dst, sizeof pnl.daddr.v4);
154 	pnl.proto = IPPROTO_TCP;
155 	pnl.sport = s_port;
156 	pnl.dport = d_port;
157 	pnl.direction = PF_IN;
158 
159 	/*
160 	 * DIOCNATLOOK does not handle PF_INOUT, so we have to check
161 	 * both directions ourselves.
162 	 */
163 	r = ioctl(dev, DIOCNATLOOK, &pnl);
164 	if (r == -1 && errno == ENOENT) {
165 		pnl.direction = PF_OUT;
166 		r = ioctl(dev, DIOCNATLOOK, &pnl);
167 	}
168 	if (r == -1)
169 		return (0);
170 
171 	/* Copy the real source address if there is NAT involved. */
172 	if (memcmp(&pnl.saddr.v4, &pnl.rsaddr.v4, sizeof pnl.saddr.v4) != 0)
173 		memcpy(src_real, &pnl.rsaddr.v4, sizeof src_real);
174 
175 	return (1);
176 }
177