1 /*        $NetBSD: ip_rcmd_pxy.c,v 1.5 2013/09/14 12:06:19 martin Exp $         */
2 
3 /*
4  * Copyright (C) 2012 by Darren Reed.
5  *
6  * See the IPFILTER.LICENCE file for details on licencing.
7  *
8  * Id: ip_rcmd_pxy.c,v 1.1.1.2 2012/07/22 13:45:33 darrenr Exp
9  *
10  * Simple RCMD transparent proxy for in-kernel use.  For use with the NAT
11  * code.
12  */
13 
14 #include <sys/cdefs.h>
15 __KERNEL_RCSID(1, "$NetBSD: ip_rcmd_pxy.c,v 1.5 2013/09/14 12:06:19 martin Exp $");
16 
17 #define   IPF_RCMD_PROXY
18 
19 typedef struct rcmdinfo {
20           u_32_t    rcmd_port;          /* Port number seen */
21           u_32_t    rcmd_portseq;       /* Sequence number where port is first seen */
22           ipnat_t   *rcmd_rule;         /* Template rule for back connection */
23 } rcmdinfo_t;
24 
25 void ipf_p_rcmd_main_load(void);
26 void ipf_p_rcmd_main_unload(void);
27 
28 int ipf_p_rcmd_init(void);
29 void ipf_p_rcmd_fini(void);
30 void ipf_p_rcmd_del(ipf_main_softc_t *, ap_session_t *);
31 int ipf_p_rcmd_new(void *, fr_info_t *, ap_session_t *, nat_t *);
32 int ipf_p_rcmd_out(void *, fr_info_t *, ap_session_t *, nat_t *);
33 int ipf_p_rcmd_in(void *, fr_info_t *, ap_session_t *, nat_t *);
34 u_short ipf_rcmd_atoi(char *);
35 int ipf_p_rcmd_portmsg(fr_info_t *, ap_session_t *, nat_t *);
36 
37 static    frentry_t rcmdfr;
38 
39 static    int                 rcmd_proxy_init = 0;
40 
41 
42 /*
43  * RCMD application proxy initialization.
44  */
45 void
ipf_p_rcmd_main_load(void)46 ipf_p_rcmd_main_load(void)
47 {
48           bzero((char *)&rcmdfr, sizeof(rcmdfr));
49           rcmdfr.fr_ref = 1;
50           rcmdfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE;
51           MUTEX_INIT(&rcmdfr.fr_lock, "RCMD proxy rule lock");
52           rcmd_proxy_init = 1;
53 }
54 
55 
56 void
ipf_p_rcmd_main_unload(void)57 ipf_p_rcmd_main_unload(void)
58 {
59           if (rcmd_proxy_init == 1) {
60                     MUTEX_DESTROY(&rcmdfr.fr_lock);
61                     rcmd_proxy_init = 0;
62           }
63 }
64 
65 
66 /*
67  * Setup for a new RCMD proxy.
68  */
69 int
ipf_p_rcmd_new(void * arg,fr_info_t * fin,ap_session_t * aps,nat_t * nat)70 ipf_p_rcmd_new(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat)
71 {
72           tcphdr_t *tcp = (tcphdr_t *)fin->fin_dp;
73           rcmdinfo_t *rc;
74           ipnat_t *ipn;
75 
76           fin = fin;          /* LINT */
77 
78           KMALLOC(rc, rcmdinfo_t *);
79           if (rc == NULL) {
80                     printf("ipf_p_rcmd_new:KMALLOCS(%zu) failed\n", sizeof(*rc));
81                     return -1;
82           }
83           aps->aps_sport = tcp->th_sport;
84           aps->aps_dport = tcp->th_dport;
85 
86           ipn = ipf_proxy_rule_rev(nat);
87           if (ipn == NULL) {
88                     KFREE(rc);
89                     return -1;
90           }
91 
92           aps->aps_data = rc;
93           aps->aps_psiz = sizeof(*rc);
94           bzero((char *)rc, sizeof(*rc));
95 
96           rc->rcmd_rule = ipn;
97 
98           return 0;
99 }
100 
101 
102 void
ipf_p_rcmd_del(ipf_main_softc_t * softc,ap_session_t * aps)103 ipf_p_rcmd_del(ipf_main_softc_t *softc, ap_session_t *aps)
104 {
105           rcmdinfo_t *rci;
106 
107           rci = aps->aps_data;
108           if (rci != NULL) {
109                     rci->rcmd_rule->in_flags |= IPN_DELETE;
110                     ipf_nat_rule_deref(softc, &rci->rcmd_rule);
111           }
112 }
113 
114 
115 /*
116  * ipf_rcmd_atoi - implement a simple version of atoi
117  */
118 u_short
ipf_rcmd_atoi(char * ptr)119 ipf_rcmd_atoi(char *ptr)
120 {
121           char *s = ptr, c;
122           u_short i = 0;
123 
124           while (((c = *s++) != '\0') && ISDIGIT(c)) {
125                     i *= 10;
126                     i += c - '0';
127           }
128           return i;
129 }
130 
131 
132 int
ipf_p_rcmd_portmsg(fr_info_t * fin,ap_session_t * aps,nat_t * nat)133 ipf_p_rcmd_portmsg(fr_info_t *fin, ap_session_t *aps, nat_t *nat)
134 {
135           tcphdr_t *tcp, tcph, *tcp2 = &tcph;
136           int off, dlen, nflags, direction;
137           ipf_main_softc_t *softc;
138           ipf_nat_softc_t *softn;
139           char portbuf[8], *s;
140           rcmdinfo_t *rc;
141           fr_info_t fi;
142           u_short sp;
143           nat_t *nat2;
144 #ifdef USE_INET6
145           ip6_t *ip6;
146 #endif
147           int tcpsz;
148           int slen = 0;
149           ip_t *ip;
150           mb_t *m;
151 
152           tcp = (tcphdr_t *)fin->fin_dp;
153 
154           m = fin->fin_m;
155           ip = fin->fin_ip;
156           tcpsz = TCP_OFF(tcp) << 2;
157 #ifdef USE_INET6
158           ip6 = (ip6_t *)fin->fin_ip;
159 #endif
160           softc = fin->fin_main_soft;
161           softn = softc->ipf_nat_soft;
162           off = (char *)tcp - (char *)ip + tcpsz + fin->fin_ipoff;
163 
164           dlen = fin->fin_dlen - tcpsz;
165           if (dlen <= 0)
166                     return 0;
167 
168           rc = (rcmdinfo_t *)aps->aps_data;
169           if ((rc->rcmd_portseq != 0) &&
170               (tcp->th_seq != rc->rcmd_portseq))
171                     return 0;
172 
173           bzero(portbuf, sizeof(portbuf));
174           COPYDATA(m, off, MIN(sizeof(portbuf), dlen), portbuf);
175 
176           portbuf[sizeof(portbuf) - 1] = '\0';
177           s = portbuf;
178           sp = ipf_rcmd_atoi(s);
179           if (sp == 0) {
180 #ifdef IP_RCMD_PROXY_DEBUG
181                     printf("ipf_p_rcmd_portmsg:sp == 0 dlen %d [%s]\n",
182                            dlen, portbuf);
183 #endif
184                     return 0;
185           }
186 
187           if (rc->rcmd_port != 0 && sp != rc->rcmd_port) {
188 #ifdef IP_RCMD_PROXY_DEBUG
189                     printf("ipf_p_rcmd_portmsg:sp(%d) != rcmd_port(%d)\n",
190                            sp, rc->rcmd_port);
191 #endif
192                     return 0;
193           }
194 
195           rc->rcmd_port = sp;
196           rc->rcmd_portseq = tcp->th_seq;
197 
198           /*
199            * Initialise the packet info structure so we can search the NAT
200            * table to see if there already is soemthing present that matches
201            * up with what we want to add.
202            */
203           bcopy((char *)fin, (char *)&fi, sizeof(fi));
204           fi.fin_flx |= FI_IGNORE;
205           fi.fin_data[0] = 0;
206           fi.fin_data[1] = sp;
207           fi.fin_src6 = nat->nat_ndst6;
208           fi.fin_dst6 = nat->nat_nsrc6;
209 
210           if (nat->nat_v[0] == 6) {
211 #ifdef USE_INET6
212                     if (nat->nat_dir == NAT_OUTBOUND) {
213                               nat2 = ipf_nat6_outlookup(&fi, NAT_SEARCH|IPN_TCP,
214                                                               nat->nat_pr[1],
215                                                               &nat->nat_osrc6.in6,
216                                                               &nat->nat_odst6.in6);
217                     } else {
218                               nat2 = ipf_nat6_inlookup(&fi, NAT_SEARCH|IPN_TCP,
219                                                              nat->nat_pr[0],
220                                                              &nat->nat_osrc6.in6,
221                                                              &nat->nat_odst6.in6);
222                     }
223 #else
224                     nat2 = (void *)-1;
225 #endif
226           } else {
227                     if (nat->nat_dir == NAT_OUTBOUND) {
228                               nat2 = ipf_nat_outlookup(&fi, NAT_SEARCH|IPN_TCP,
229                                                              nat->nat_pr[1],
230                                                              nat->nat_osrcip,
231                                                              nat->nat_odstip);
232                     } else {
233                               nat2 = ipf_nat_inlookup(&fi, NAT_SEARCH|IPN_TCP,
234                                                             nat->nat_pr[0],
235                                                             nat->nat_osrcip,
236                                                             nat->nat_odstip);
237                     }
238           }
239           if (nat2 != NULL)
240                     return APR_ERR(1);
241 
242           /*
243            * Add skeleton NAT entry for connection which will come
244            * back the other way.
245            */
246 
247           if (nat->nat_v[0] == 6) {
248 #ifdef USE_INET6
249                     slen = ip6->ip6_plen;
250                     ip6->ip6_plen = htons(sizeof(*tcp));
251 #endif
252           } else {
253                     slen = ip->ip_len;
254                     ip->ip_len = htons(fin->fin_hlen + sizeof(*tcp));
255           }
256 
257           /*
258            * Fill out the fake TCP header with a few fields that ipfilter
259            * considers to be important.
260            */
261           bzero((char *)tcp2, sizeof(*tcp2));
262           tcp2->th_win = htons(8192);
263           TCP_OFF_A(tcp2, 5);
264           tcp2->th_flags = TH_SYN;
265 
266           fi.fin_dp = (char *)tcp2;
267           fi.fin_fr = &rcmdfr;
268           fi.fin_dlen = sizeof(*tcp2);
269           fi.fin_plen = fi.fin_hlen + sizeof(*tcp2);
270           fi.fin_flx &= FI_LOWTTL|FI_FRAG|FI_TCPUDP|FI_OPTIONS|FI_IGNORE;
271 
272           if (nat->nat_dir == NAT_OUTBOUND) {
273                     fi.fin_out = 0;
274                     direction = NAT_INBOUND;
275           } else {
276                     fi.fin_out = 1;
277                     direction = NAT_OUTBOUND;
278           }
279           nflags = SI_W_SPORT|NAT_SLAVE|IPN_TCP;
280 
281           MUTEX_ENTER(&softn->ipf_nat_new);
282           if (fin->fin_v == 4)
283                     nat2 = ipf_nat_add(&fi, rc->rcmd_rule, NULL, nflags,
284                                            direction);
285 #ifdef USE_INET6
286           else
287                     nat2 = ipf_nat6_add(&fi, rc->rcmd_rule, NULL, nflags,
288                                             direction);
289 #endif
290           MUTEX_EXIT(&softn->ipf_nat_new);
291 
292           if (nat2 != NULL) {
293                     (void) ipf_nat_proto(&fi, nat2, IPN_TCP);
294                     MUTEX_ENTER(&nat2->nat_lock);
295                     ipf_nat_update(&fi, nat2);
296                     MUTEX_EXIT(&nat2->nat_lock);
297                     fi.fin_ifp = NULL;
298                     if (nat2->nat_dir == NAT_INBOUND)
299                               fi.fin_dst6 = nat->nat_osrc6;
300                     (void) ipf_state_add(softc, &fi, NULL, SI_W_SPORT);
301           }
302           if (nat->nat_v[0] == 6) {
303 #ifdef USE_INET6
304                     ip6->ip6_plen = slen;
305 #endif
306           } else {
307                     ip->ip_len = slen;
308           }
309           if (nat2 == NULL)
310                     return APR_ERR(1);
311           return 0;
312 }
313 
314 
315 int
ipf_p_rcmd_out(void * arg,fr_info_t * fin,ap_session_t * aps,nat_t * nat)316 ipf_p_rcmd_out(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat)
317 {
318           if (nat->nat_dir == NAT_OUTBOUND)
319                     return ipf_p_rcmd_portmsg(fin, aps, nat);
320           return 0;
321 }
322 
323 
324 int
ipf_p_rcmd_in(void * arg,fr_info_t * fin,ap_session_t * aps,nat_t * nat)325 ipf_p_rcmd_in(void *arg, fr_info_t *fin, ap_session_t *aps, nat_t *nat)
326 {
327           if (nat->nat_dir == NAT_INBOUND)
328                     return ipf_p_rcmd_portmsg(fin, aps, nat);
329           return 0;
330 }
331