1254401Scy/*
2254401Scy * Copyright (C) 2012 by Darren Reed.
3254401Scy *
4254401Scy * See the IPFILTER.LICENCE file for details on licencing.
5254401Scy *
6254401Scy * $Id: ip_tftp_pxy.c,v 1.1.2.9 2012/07/22 08:04:23 darren_r Exp $
7254401Scy */
8254401Scy
9254401Scy#define IPF_TFTP_PROXY
10254401Scy
11254401Scytypedef struct ipf_tftp_softc_s {
12254401Scy        int     	ipf_p_tftp_readonly;
13254401Scy	ipftuneable_t	*ipf_p_tftp_tune;
14254401Scy} ipf_tftp_softc_t;
15254401Scy
16369245Sgit2svnint ipf_p_tftp_backchannel(fr_info_t *, ap_session_t *, nat_t *);
17369245Sgit2svnint ipf_p_tftp_client(ipf_tftp_softc_t *, fr_info_t *, ap_session_t *,
18369245Sgit2svn			   nat_t *);
19369245Sgit2svnint ipf_p_tftp_in(void *, fr_info_t *, ap_session_t *, nat_t *);
20369245Sgit2svnvoid ipf_p_tftp_main_load(void);
21369245Sgit2svnvoid ipf_p_tftp_main_unload(void);
22369245Sgit2svnint ipf_p_tftp_new(void *, fr_info_t *, ap_session_t *, nat_t *);
23369245Sgit2svnvoid ipf_p_tftp_del(ipf_main_softc_t *, ap_session_t *);
24369245Sgit2svnint ipf_p_tftp_out(void *, fr_info_t *, ap_session_t *, nat_t *);
25369245Sgit2svnint ipf_p_tftp_server(ipf_tftp_softc_t *, fr_info_t *, ap_session_t *,
26369245Sgit2svn			   nat_t *);
27369245Sgit2svnvoid *ipf_p_tftp_soft_create(ipf_main_softc_t *);
28369245Sgit2svnvoid ipf_p_tftp_soft_destroy(ipf_main_softc_t *, void *);
29254401Scy
30254401Scystatic	frentry_t	tftpfr;
31254401Scystatic	int		tftp_proxy_init = 0;
32254401Scy
33254401Scytypedef enum tftp_cmd_e {
34254401Scy	TFTP_CMD_READ = 1,
35254401Scy	TFTP_CMD_WRITE = 2,
36254401Scy	TFTP_CMD_DATA = 3,
37254401Scy	TFTP_CMD_ACK = 4,
38254401Scy	TFTP_CMD_ERROR = 5
39254401Scy} tftp_cmd_t;
40254401Scy
41254401Scytypedef struct tftpinfo {
42254401Scy	tftp_cmd_t	ti_lastcmd;
43254401Scy	int		ti_nextblk;
44254401Scy	int		ti_lastblk;
45254401Scy	int		ti_lasterror;
46254401Scy	char		ti_filename[80];
47254401Scy	ipnat_t		*ti_rule;
48254401Scy} tftpinfo_t;
49254401Scy
50254401Scystatic  ipftuneable_t   ipf_tftp_tuneables[] = {
51254401Scy	{ { (void *)offsetof(ipf_tftp_softc_t, ipf_p_tftp_readonly) },
52254401Scy		"tftp_read_only",	0,	1,
53254401Scy		stsizeof(ipf_tftp_softc_t, ipf_p_tftp_readonly),
54254401Scy		0, NULL, NULL },
55254401Scy	{ { NULL }, NULL, 0, 0, 0, 0, NULL, NULL }
56254401Scy};
57254401Scy
58254401Scy
59254401Scy/*
60254401Scy * TFTP application proxy initialization.
61254401Scy */
62254401Scyvoid
63254401Scyipf_p_tftp_main_load()
64254401Scy{
65254401Scy
66254401Scy	bzero((char *)&tftpfr, sizeof(tftpfr));
67254401Scy	tftpfr.fr_ref = 1;
68254401Scy	tftpfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE;
69254401Scy	MUTEX_INIT(&tftpfr.fr_lock, "TFTP proxy rule lock");
70254401Scy	tftp_proxy_init = 1;
71254401Scy}
72254401Scy
73254401Scy
74254401Scyvoid
75254401Scyipf_p_tftp_main_unload()
76254401Scy{
77254401Scy
78254401Scy	if (tftp_proxy_init == 1) {
79254401Scy		MUTEX_DESTROY(&tftpfr.fr_lock);
80254401Scy		tftp_proxy_init = 0;
81254401Scy	}
82254401Scy}
83254401Scy
84254401Scy
85254401Scyvoid *
86254401Scyipf_p_tftp_soft_create(softc)
87254401Scy	ipf_main_softc_t *softc;
88254401Scy{
89254401Scy	ipf_tftp_softc_t *softt;
90254401Scy
91254401Scy	KMALLOC(softt, ipf_tftp_softc_t *);
92254401Scy	if (softt == NULL)
93254401Scy		return NULL;
94254401Scy
95254401Scy	bzero((char *)softt, sizeof(*softt));
96254401Scy
97254401Scy	softt->ipf_p_tftp_tune = ipf_tune_array_copy(softt,
98254401Scy						     sizeof(ipf_tftp_tuneables),
99254401Scy						     ipf_tftp_tuneables);
100254401Scy	if (softt->ipf_p_tftp_tune == NULL) {
101254401Scy		ipf_p_tftp_soft_destroy(softc, softt);
102254401Scy		return NULL;
103254401Scy	}
104254401Scy	if (ipf_tune_array_link(softc, softt->ipf_p_tftp_tune) == -1) {
105254401Scy		ipf_p_tftp_soft_destroy(softc, softt);
106254401Scy		return NULL;
107254401Scy	}
108254401Scy
109254401Scy	softt->ipf_p_tftp_readonly = 1;
110254401Scy
111254401Scy	return softt;
112254401Scy}
113254401Scy
114254401Scy
115254401Scyvoid
116254401Scyipf_p_tftp_soft_destroy(softc, arg)
117254401Scy        ipf_main_softc_t *softc;
118254401Scy        void *arg;
119254401Scy{
120254401Scy	ipf_tftp_softc_t *softt = arg;
121254401Scy
122254401Scy	if (softt->ipf_p_tftp_tune != NULL) {
123254401Scy		ipf_tune_array_unlink(softc, softt->ipf_p_tftp_tune);
124254401Scy		KFREES(softt->ipf_p_tftp_tune, sizeof(ipf_tftp_tuneables));
125254401Scy		softt->ipf_p_tftp_tune = NULL;
126254401Scy	}
127254401Scy
128254401Scy	KFREE(softt);
129254401Scy}
130254401Scy
131254401Scy
132254401Scyint
133254401Scyipf_p_tftp_out(arg, fin, aps, nat)
134254401Scy	void *arg;
135254401Scy	fr_info_t *fin;
136254401Scy	ap_session_t *aps;
137254401Scy	nat_t *nat;
138254401Scy{
139254401Scy	ipf_tftp_softc_t *softt = arg;
140254401Scy
141254401Scy	fin->fin_flx |= FI_NOWILD;
142254401Scy	if (nat->nat_dir == NAT_OUTBOUND)
143254401Scy		return ipf_p_tftp_client(softt, fin, aps, nat);
144254401Scy	return ipf_p_tftp_server(softt, fin, aps, nat);
145254401Scy}
146254401Scy
147254401Scy
148254401Scyint
149254401Scyipf_p_tftp_in(arg, fin, aps, nat)
150254401Scy	void *arg;
151254401Scy	fr_info_t *fin;
152254401Scy	ap_session_t *aps;
153254401Scy	nat_t *nat;
154254401Scy{
155254401Scy	ipf_tftp_softc_t *softt = arg;
156254401Scy
157254401Scy	fin->fin_flx |= FI_NOWILD;
158254401Scy	if (nat->nat_dir == NAT_INBOUND)
159254401Scy		return ipf_p_tftp_client(softt, fin, aps, nat);
160254401Scy	return ipf_p_tftp_server(softt, fin, aps, nat);
161254401Scy}
162254401Scy
163254401Scy
164254401Scyint
165254401Scyipf_p_tftp_new(arg, fin, aps, nat)
166254401Scy	void *arg;
167254401Scy	fr_info_t *fin;
168254401Scy	ap_session_t *aps;
169254401Scy	nat_t *nat;
170254401Scy{
171254401Scy	udphdr_t *udp;
172254401Scy	tftpinfo_t *ti;
173254401Scy	ipnat_t *ipn;
174254401Scy	ipnat_t *np;
175254401Scy	int size;
176254401Scy
177254401Scy	fin = fin;	/* LINT */
178254401Scy
179254401Scy	np = nat->nat_ptr;
180254401Scy	size = np->in_size;
181254401Scy
182254401Scy	KMALLOC(ti, tftpinfo_t *);
183254401Scy	if (ti == NULL)
184254401Scy		return -1;
185254401Scy	KMALLOCS(ipn, ipnat_t *, size);
186254401Scy	if (ipn == NULL) {
187254401Scy		KFREE(ti);
188254401Scy		return -1;
189254401Scy	}
190254401Scy
191254401Scy	aps->aps_data = ti;
192254401Scy	aps->aps_psiz = sizeof(*ti);
193254401Scy	bzero((char *)ti, sizeof(*ti));
194254401Scy	bzero((char *)ipn, size);
195254401Scy	ti->ti_rule = ipn;
196254401Scy
197254401Scy	udp = (udphdr_t *)fin->fin_dp;
198254401Scy	aps->aps_sport = udp->uh_sport;
199254401Scy	aps->aps_dport = udp->uh_dport;
200254401Scy
201254401Scy	ipn->in_size = size;
202254401Scy	ipn->in_apr = NULL;
203254401Scy	ipn->in_use = 1;
204254401Scy	ipn->in_hits = 1;
205254401Scy	ipn->in_ippip = 1;
206254401Scy	ipn->in_pr[0] = IPPROTO_UDP;
207254401Scy	ipn->in_pr[1] = IPPROTO_UDP;
208254401Scy	ipn->in_ifps[0] = nat->nat_ifps[0];
209254401Scy	ipn->in_ifps[1] = nat->nat_ifps[1];
210254401Scy	ipn->in_v[0] = nat->nat_ptr->in_v[1];
211254401Scy	ipn->in_v[1] = nat->nat_ptr->in_v[0];
212254401Scy	ipn->in_flags = IPN_UDP|IPN_FIXEDDPORT|IPN_PROXYRULE;
213254401Scy
214254401Scy	ipn->in_nsrcip6 = nat->nat_odst6;
215254401Scy	ipn->in_osrcip6 = nat->nat_ndst6;
216254401Scy
217254401Scy	if ((np->in_redir & NAT_REDIRECT) != 0) {
218254401Scy		ipn->in_redir = NAT_MAP;
219254401Scy		if (ipn->in_v[0] == 4) {
220254401Scy			ipn->in_snip = ntohl(nat->nat_odstaddr);
221254401Scy			ipn->in_dnip = ntohl(nat->nat_nsrcaddr);
222254401Scy		} else {
223254401Scy#ifdef USE_INET6
224254401Scy			ipn->in_snip6 = nat->nat_odst6;
225254401Scy			ipn->in_dnip6 = nat->nat_nsrc6;
226254401Scy#endif
227254401Scy		}
228254401Scy		ipn->in_ndstip6 = nat->nat_nsrc6;
229254401Scy		ipn->in_odstip6 = nat->nat_osrc6;
230254401Scy	} else {
231254401Scy		ipn->in_redir = NAT_REDIRECT;
232254401Scy		if (ipn->in_v[0] == 4) {
233254401Scy			ipn->in_snip = ntohl(nat->nat_odstaddr);
234254401Scy			ipn->in_dnip = ntohl(nat->nat_osrcaddr);
235254401Scy		} else {
236254401Scy#ifdef USE_INET6
237254401Scy			ipn->in_snip6 = nat->nat_odst6;
238254401Scy			ipn->in_dnip6 = nat->nat_osrc6;
239254401Scy#endif
240254401Scy		}
241254401Scy		ipn->in_ndstip6 = nat->nat_osrc6;
242254401Scy		ipn->in_odstip6 = nat->nat_nsrc6;
243254401Scy	}
244254401Scy	ipn->in_odport = htons(fin->fin_sport);
245254401Scy	ipn->in_ndport = htons(fin->fin_sport);
246254401Scy
247254401Scy	IP6_SETONES(&ipn->in_osrcmsk6);
248254401Scy	IP6_SETONES(&ipn->in_nsrcmsk6);
249254401Scy	IP6_SETONES(&ipn->in_odstmsk6);
250254401Scy	IP6_SETONES(&ipn->in_ndstmsk6);
251254401Scy	MUTEX_INIT(&ipn->in_lock, "tftp proxy NAT rule");
252254401Scy
253254401Scy	ipn->in_namelen = np->in_namelen;
254254401Scy	bcopy(np->in_names, ipn->in_ifnames, ipn->in_namelen);
255254401Scy	ipn->in_ifnames[0] = np->in_ifnames[0];
256254401Scy	ipn->in_ifnames[1] = np->in_ifnames[1];
257254401Scy
258254401Scy	ti->ti_lastcmd = 0;
259254401Scy
260254401Scy	return 0;
261254401Scy}
262254401Scy
263254401Scy
264254401Scyvoid
265254401Scyipf_p_tftp_del(softc, aps)
266254401Scy	ipf_main_softc_t *softc;
267254401Scy	ap_session_t *aps;
268254401Scy{
269254401Scy	tftpinfo_t *tftp;
270254401Scy
271254401Scy	tftp = aps->aps_data;
272254401Scy	if (tftp != NULL) {
273254401Scy		tftp->ti_rule->in_flags |= IPN_DELETE;
274254401Scy		ipf_nat_rule_deref(softc, &tftp->ti_rule);
275254401Scy	}
276254401Scy}
277254401Scy
278254401Scy
279254401Scy/*
280254401Scy * Setup for a new TFTP proxy.
281254401Scy */
282254401Scyint
283254401Scyipf_p_tftp_backchannel(fin, aps, nat)
284254401Scy	fr_info_t *fin;
285254401Scy	ap_session_t *aps;
286254401Scy	nat_t *nat;
287254401Scy{
288254401Scy	ipf_main_softc_t *softc = fin->fin_main_soft;
289254401Scy#ifdef USE_MUTEXES
290254401Scy	ipf_nat_softc_t *softn = softc->ipf_nat_soft;
291254401Scy#endif
292254401Scy#ifdef USE_INET6
293254401Scy	i6addr_t swip6, sw2ip6;
294254401Scy	ip6_t *ip6;
295254401Scy#endif
296254401Scy	struct in_addr swip, sw2ip;
297254401Scy	tftpinfo_t *ti;
298254401Scy	udphdr_t udp;
299254401Scy	fr_info_t fi;
300256253Sdim	u_short slen = 0; /* silence gcc */
301254401Scy	nat_t *nat2;
302254401Scy	int nflags;
303254401Scy	ip_t *ip;
304254401Scy	int dir;
305254401Scy
306254401Scy	ti = aps->aps_data;
307254401Scy	/*
308254401Scy	 * Add skeleton NAT entry for connection which will come back the
309254401Scy	 * other way.
310254401Scy	 */
311254401Scy	bcopy((char *)fin, (char *)&fi, sizeof(fi));
312254401Scy	fi.fin_flx |= FI_IGNORE;
313254401Scy	fi.fin_data[1] = 0;
314254401Scy
315254401Scy	bzero((char *)&udp, sizeof(udp));
316254401Scy	udp.uh_sport = 0;	/* XXX - don't specify remote port */
317254401Scy	udp.uh_dport = ti->ti_rule->in_ndport;
318254401Scy	udp.uh_ulen = htons(sizeof(udp));
319254401Scy	udp.uh_sum = 0;
320254401Scy
321254401Scy	fi.fin_fr = &tftpfr;
322254401Scy	fi.fin_dp = (char *)&udp;
323254401Scy	fi.fin_sport = 0;
324254401Scy	fi.fin_dport = ntohs(ti->ti_rule->in_ndport);
325254401Scy	fi.fin_dlen = sizeof(udp);
326254401Scy	fi.fin_plen = fi.fin_hlen + sizeof(udp);
327254401Scy	fi.fin_flx &= FI_LOWTTL|FI_FRAG|FI_TCPUDP|FI_OPTIONS|FI_IGNORE;
328254401Scy	nflags = NAT_SLAVE|IPN_UDP|SI_W_SPORT;
329254401Scy#ifdef USE_INET6
330254401Scy	ip6 = (ip6_t *)fin->fin_ip;
331254401Scy#endif
332254401Scy	ip = fin->fin_ip;
333254401Scy	sw2ip.s_addr = 0;
334254401Scy	swip.s_addr = 0;
335254401Scy
336254401Scy	fi.fin_src6 = nat->nat_ndst6;
337254401Scy	fi.fin_dst6 = nat->nat_nsrc6;
338254401Scy	if (nat->nat_v[0] == 4) {
339254401Scy		slen = ip->ip_len;
340254401Scy		ip->ip_len = htons(fin->fin_hlen + sizeof(udp));
341254401Scy		swip = ip->ip_src;
342254401Scy		sw2ip = ip->ip_dst;
343254401Scy		ip->ip_src = nat->nat_ndstip;
344254401Scy		ip->ip_dst = nat->nat_nsrcip;
345254401Scy	} else {
346254401Scy#ifdef USE_INET6
347254401Scy		slen = ip6->ip6_plen;
348254401Scy		ip6->ip6_plen = htons(sizeof(udp));
349254401Scy		swip6.in6 = ip6->ip6_src;
350254401Scy		sw2ip6.in6 = ip6->ip6_dst;
351254401Scy		ip6->ip6_src = nat->nat_ndst6.in6;
352254401Scy		ip6->ip6_dst = nat->nat_nsrc6.in6;
353254401Scy#endif
354254401Scy	}
355254401Scy
356254401Scy	if (nat->nat_dir == NAT_INBOUND) {
357254401Scy		dir = NAT_OUTBOUND;
358254401Scy		fi.fin_out = 1;
359254401Scy	} else {
360254401Scy		dir = NAT_INBOUND;
361254401Scy		fi.fin_out = 0;
362254401Scy	}
363254401Scy	nflags |= NAT_NOTRULEPORT;
364254401Scy
365254401Scy	MUTEX_ENTER(&softn->ipf_nat_new);
366255332Scy#ifdef USE_INET6
367255332Scy	if (nat->nat_v[0] == 6)
368255332Scy		nat2 = ipf_nat6_add(&fi, ti->ti_rule, NULL, nflags, dir);
369255332Scy	else
370255332Scy#endif
371254401Scy		nat2 = ipf_nat_add(&fi, ti->ti_rule, NULL, nflags, dir);
372254401Scy	MUTEX_EXIT(&softn->ipf_nat_new);
373254401Scy	if (nat2 != NULL) {
374254401Scy		(void) ipf_nat_proto(&fi, nat2, IPN_UDP);
375254401Scy		ipf_nat_update(&fi, nat2);
376254401Scy		fi.fin_ifp = NULL;
377254401Scy		if (ti->ti_rule->in_redir == NAT_MAP) {
378254401Scy			fi.fin_src6 = nat->nat_ndst6;
379254401Scy			fi.fin_dst6 = nat->nat_nsrc6;
380254401Scy			if (nat->nat_v[0] == 4) {
381254401Scy				ip->ip_src = nat->nat_ndstip;
382254401Scy				ip->ip_dst = nat->nat_nsrcip;
383254401Scy			} else {
384254401Scy#ifdef USE_INET6
385254401Scy				ip6->ip6_src = nat->nat_ndst6.in6;
386254401Scy				ip6->ip6_dst = nat->nat_nsrc6.in6;
387254401Scy#endif
388254401Scy			}
389254401Scy		} else {
390254401Scy			fi.fin_src6 = nat->nat_odst6;
391254401Scy			fi.fin_dst6 = nat->nat_osrc6;
392254401Scy			if (fin->fin_v == 4) {
393254401Scy				ip->ip_src = nat->nat_odstip;
394254401Scy				ip->ip_dst = nat->nat_osrcip;
395254401Scy			} else {
396254401Scy#ifdef USE_INET6
397254401Scy				ip6->ip6_src = nat->nat_odst6.in6;
398254401Scy				ip6->ip6_dst = nat->nat_osrc6.in6;
399254401Scy#endif
400254401Scy			}
401254401Scy		}
402254401Scy		if (ipf_state_add(softc, &fi, NULL, SI_W_SPORT) != 0) {
403254401Scy			ipf_nat_setpending(softc, nat2);
404254401Scy		}
405254401Scy	}
406254401Scy	if (nat->nat_v[0] == 4) {
407254401Scy		ip->ip_len = slen;
408254401Scy		ip->ip_src = swip;
409254401Scy		ip->ip_dst = sw2ip;
410254401Scy	} else {
411254401Scy#ifdef USE_INET6
412254401Scy		ip6->ip6_plen = slen;
413254401Scy		ip6->ip6_src = swip6.in6;
414254401Scy		ip6->ip6_dst = sw2ip6.in6;
415254401Scy#endif
416254401Scy	}
417254401Scy	return 0;
418254401Scy}
419254401Scy
420254401Scy
421254401Scyint
422254401Scyipf_p_tftp_client(softt, fin, aps, nat)
423254401Scy	ipf_tftp_softc_t *softt;
424254401Scy	fr_info_t *fin;
425254401Scy	ap_session_t *aps;
426254401Scy	nat_t *nat;
427254401Scy{
428254401Scy	u_char *msg, *s, *t;
429254401Scy	tftpinfo_t *ti;
430254401Scy	u_short opcode;
431254401Scy	udphdr_t *udp;
432254401Scy	int len;
433254401Scy
434254401Scy	if (fin->fin_dlen < 4)
435254401Scy		return 0;
436254401Scy
437254401Scy	ti = aps->aps_data;
438254401Scy	msg = fin->fin_dp;
439254401Scy	msg += sizeof(udphdr_t);
440254401Scy	opcode = (msg[0] << 8) | msg[1];
441254401Scy	DT3(tftp_cmd, fr_info_t *, fin, int, opcode, nat_t *, nat);
442254401Scy
443254401Scy	switch (opcode)
444254401Scy	{
445254401Scy	case TFTP_CMD_WRITE :
446254401Scy		if (softt->ipf_p_tftp_readonly != 0)
447254401Scy			break;
448254401Scy		/* FALLTHROUGH */
449254401Scy	case TFTP_CMD_READ :
450254401Scy		len = fin->fin_dlen - sizeof(*udp) - 2;
451254401Scy		if (len > sizeof(ti->ti_filename) - 1)
452254401Scy			len = sizeof(ti->ti_filename) - 1;
453254401Scy		s = msg + 2;
454254401Scy		for (t = (u_char *)ti->ti_filename; (len > 0); len--, s++) {
455254401Scy			*t++ = *s;
456254401Scy			if (*s == '\0')
457254401Scy				break;
458254401Scy		}
459254401Scy		ipf_p_tftp_backchannel(fin, aps, nat);
460254401Scy		break;
461254401Scy	default :
462254401Scy		return -1;
463254401Scy	}
464254401Scy
465254401Scy	ti = aps->aps_data;
466254401Scy	ti->ti_lastcmd = opcode;
467254401Scy	return 0;
468254401Scy}
469254401Scy
470254401Scy
471254401Scyint
472254401Scyipf_p_tftp_server(softt, fin, aps, nat)
473254401Scy	ipf_tftp_softc_t *softt;
474254401Scy	fr_info_t *fin;
475254401Scy	ap_session_t *aps;
476254401Scy	nat_t *nat;
477254401Scy{
478254401Scy	tftpinfo_t *ti;
479254401Scy	u_short opcode;
480254401Scy	u_short arg;
481254401Scy	u_char *msg;
482254401Scy
483254401Scy	if (fin->fin_dlen < 4)
484254401Scy		return 0;
485254401Scy
486254401Scy	ti = aps->aps_data;
487254401Scy	msg = fin->fin_dp;
488254401Scy	msg += sizeof(udphdr_t);
489254401Scy	arg = (msg[2] << 8) | msg[3];
490254401Scy	opcode = (msg[0] << 8) | msg[1];
491254401Scy
492254401Scy	switch (opcode)
493254401Scy	{
494254401Scy	case TFTP_CMD_ACK :
495254401Scy		ti->ti_lastblk = arg;
496254401Scy		break;
497254401Scy
498254401Scy	case TFTP_CMD_ERROR :
499254401Scy		ti->ti_lasterror = arg;
500254401Scy		break;
501254401Scy
502254401Scy	default :
503254401Scy		return -1;
504254401Scy	}
505254401Scy
506254401Scy	ti->ti_lastcmd = opcode;
507254401Scy	return 0;
508254401Scy}
509