1145516Sdarrenr/*
2255332Scy * Copyright (C) 2012 by Darren Reed.
3145516Sdarrenr *
4145516Sdarrenr * See the IPFILTER.LICENCE file for details on licencing.
5145516Sdarrenr *
6255332Scy * $Id$
7145516Sdarrenr */
8145516Sdarrenr
9145516Sdarrenr#define	IPF_IRC_PROXY
10145516Sdarrenr
11145516Sdarrenr#define	IPF_IRCBUFSZ	96	/* This *MUST* be >= 64! */
12145516Sdarrenr
13145516Sdarrenr
14369245Sgit2svnvoid ipf_p_irc_main_load(void);
15369245Sgit2svnvoid ipf_p_irc_main_unload(void);
16369245Sgit2svnint ipf_p_irc_new(void *, fr_info_t *, ap_session_t *, nat_t *);
17369245Sgit2svnint ipf_p_irc_out(void *, fr_info_t *, ap_session_t *, nat_t *);
18369245Sgit2svnint ipf_p_irc_send(fr_info_t *, nat_t *);
19369245Sgit2svnint ipf_p_irc_complete(ircinfo_t *, char *, size_t);
20369245Sgit2svnu_short ipf_irc_atoi(char **);
21145516Sdarrenr
22145516Sdarrenrstatic	frentry_t	ircnatfr;
23145516Sdarrenr
24145516Sdarrenrint	irc_proxy_init = 0;
25145516Sdarrenr
26145516Sdarrenr
27145516Sdarrenr/*
28145516Sdarrenr * Initialize local structures.
29145516Sdarrenr */
30255332Scyvoid
31255332Scyipf_p_irc_main_load()
32145516Sdarrenr{
33145516Sdarrenr	bzero((char *)&ircnatfr, sizeof(ircnatfr));
34145516Sdarrenr	ircnatfr.fr_ref = 1;
35145516Sdarrenr	ircnatfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE;
36145516Sdarrenr	MUTEX_INIT(&ircnatfr.fr_lock, "IRC proxy rule lock");
37145516Sdarrenr	irc_proxy_init = 1;
38145516Sdarrenr}
39145516Sdarrenr
40145516Sdarrenr
41255332Scyvoid
42255332Scyipf_p_irc_main_unload()
43145516Sdarrenr{
44145516Sdarrenr	if (irc_proxy_init == 1) {
45145516Sdarrenr		MUTEX_DESTROY(&ircnatfr.fr_lock);
46145516Sdarrenr		irc_proxy_init = 0;
47145516Sdarrenr	}
48145516Sdarrenr}
49145516Sdarrenr
50145516Sdarrenr
51255332Scyconst char *ipf_p_irc_dcctypes[] = {
52145516Sdarrenr	"CHAT ",	/* CHAT chat ipnumber portnumber */
53145516Sdarrenr	"SEND ",	/* SEND filename ipnumber portnumber */
54145516Sdarrenr	"MOVE ",
55145516Sdarrenr	"TSEND ",
56145516Sdarrenr	"SCHAT ",
57145516Sdarrenr	NULL,
58145516Sdarrenr};
59145516Sdarrenr
60145516Sdarrenr
61145516Sdarrenr/*
62145516Sdarrenr * :A PRIVMSG B :^ADCC CHAT chat 0 0^A\r\n
63145516Sdarrenr * PRIVMSG B ^ADCC CHAT chat 0 0^A\r\n
64145516Sdarrenr */
65145516Sdarrenr
66145516Sdarrenr
67255332Scyint
68255332Scyipf_p_irc_complete(ircp, buf, len)
69255332Scy	ircinfo_t *ircp;
70255332Scy	char *buf;
71255332Scy	size_t len;
72145516Sdarrenr{
73145516Sdarrenr	register char *s, c;
74145516Sdarrenr	register size_t i;
75145516Sdarrenr	u_32_t l;
76145516Sdarrenr	int j, k;
77145516Sdarrenr
78145516Sdarrenr	ircp->irc_ipnum = 0;
79145516Sdarrenr	ircp->irc_port = 0;
80145516Sdarrenr
81145516Sdarrenr	if (len < 31)
82145516Sdarrenr		return 0;
83145516Sdarrenr	s = buf;
84145516Sdarrenr	c = *s++;
85145516Sdarrenr	i = len - 1;
86145516Sdarrenr
87145516Sdarrenr	if ((c != ':') && (c != 'P'))
88145516Sdarrenr		return 0;
89145516Sdarrenr
90145516Sdarrenr	if (c == ':') {
91145516Sdarrenr		/*
92145516Sdarrenr		 * Loosely check that the source is a nickname of some sort
93145516Sdarrenr		 */
94145516Sdarrenr		s++;
95145516Sdarrenr		c = *s;
96145516Sdarrenr		ircp->irc_snick = s;
97145516Sdarrenr		if (!ISALPHA(c))
98145516Sdarrenr			return 0;
99145516Sdarrenr		i--;
100145516Sdarrenr		for (c = *s; !ISSPACE(c) && (i > 0); i--)
101145516Sdarrenr			c = *s++;
102145516Sdarrenr		if (i < 31)
103145516Sdarrenr			return 0;
104145516Sdarrenr		if (c != 'P')
105145516Sdarrenr			return 0;
106145516Sdarrenr	} else
107145516Sdarrenr		ircp->irc_snick = NULL;
108145516Sdarrenr
109145516Sdarrenr	/*
110145516Sdarrenr	 * Check command string
111145516Sdarrenr	 */
112145516Sdarrenr	if (strncmp(s, "PRIVMSG ", 8))
113145516Sdarrenr		return 0;
114145516Sdarrenr	i -= 8;
115145516Sdarrenr	s += 8;
116145516Sdarrenr	c = *s;
117145516Sdarrenr	ircp->irc_dnick = s;
118145516Sdarrenr
119145516Sdarrenr	/*
120145516Sdarrenr	 * Loosely check that the destination is a nickname of some sort
121145516Sdarrenr	 */
122145516Sdarrenr	if (!ISALPHA(c))
123145516Sdarrenr		return 0;
124145516Sdarrenr	for (; !ISSPACE(c) && (i > 0); i--)
125145516Sdarrenr		c = *s++;
126145516Sdarrenr	if (i < 20)
127145516Sdarrenr		return 0;
128145516Sdarrenr	s++,
129145516Sdarrenr	i--;
130145516Sdarrenr
131145516Sdarrenr	/*
132145516Sdarrenr	 * Look for a ^A to start the DCC
133145516Sdarrenr	 */
134145516Sdarrenr	c = *s;
135145516Sdarrenr	if (c == ':') {
136145516Sdarrenr		s++;
137145516Sdarrenr		c = *s;
138145516Sdarrenr	}
139145516Sdarrenr
140145516Sdarrenr	if (strncmp(s, "\001DCC ", 4))
141145516Sdarrenr		return 0;
142145516Sdarrenr
143145516Sdarrenr	i -= 4;
144145516Sdarrenr	s += 4;
145145516Sdarrenr
146145516Sdarrenr	/*
147145516Sdarrenr	 * Check for a recognised DCC command
148145516Sdarrenr	 */
149255332Scy	for (j = 0, k = 0; ipf_p_irc_dcctypes[j]; j++) {
150255332Scy		k = MIN(strlen(ipf_p_irc_dcctypes[j]), i);
151255332Scy		if (!strncmp(ipf_p_irc_dcctypes[j], s, k))
152145516Sdarrenr			break;
153145516Sdarrenr	}
154255332Scy	if (!ipf_p_irc_dcctypes[j])
155145516Sdarrenr		return 0;
156145516Sdarrenr
157145516Sdarrenr	ircp->irc_type = s;
158145516Sdarrenr	i -= k;
159145516Sdarrenr	s += k;
160145516Sdarrenr
161145516Sdarrenr	if (i < 11)
162145516Sdarrenr		return 0;
163145516Sdarrenr
164145516Sdarrenr	/*
165145516Sdarrenr	 * Check for the arg
166145516Sdarrenr	 */
167145516Sdarrenr	c = *s;
168145516Sdarrenr	if (ISSPACE(c))
169145516Sdarrenr		return 0;
170145516Sdarrenr	ircp->irc_arg = s;
171145516Sdarrenr	for (; (c != ' ') && (c != '\001') && (i > 0); i--)
172145516Sdarrenr		c = *s++;
173145516Sdarrenr
174145516Sdarrenr	if (c == '\001')	/* In reality a ^A can quote another ^A...*/
175145516Sdarrenr		return 0;
176145516Sdarrenr
177145516Sdarrenr	if (i < 5)
178145516Sdarrenr		return 0;
179145516Sdarrenr
180145516Sdarrenr	s++;
181145516Sdarrenr	i--;
182145516Sdarrenr	c = *s;
183145516Sdarrenr	if (!ISDIGIT(c))
184145516Sdarrenr		return 0;
185145516Sdarrenr	ircp->irc_addr = s;
186145516Sdarrenr	/*
187145516Sdarrenr	 * Get the IP#
188145516Sdarrenr	 */
189145516Sdarrenr	for (l = 0; ISDIGIT(c) && (i > 0); i--) {
190145516Sdarrenr		l *= 10;
191145516Sdarrenr		l += c - '0';
192145516Sdarrenr		c = *s++;
193145516Sdarrenr	}
194145516Sdarrenr
195145516Sdarrenr	if (i < 4)
196145516Sdarrenr		return 0;
197145516Sdarrenr
198145516Sdarrenr	if (c != ' ')
199145516Sdarrenr		return 0;
200145516Sdarrenr
201145516Sdarrenr	ircp->irc_ipnum = l;
202145516Sdarrenr	s++;
203145516Sdarrenr	i--;
204145516Sdarrenr	c = *s;
205145516Sdarrenr	if (!ISDIGIT(c))
206145516Sdarrenr		return 0;
207145516Sdarrenr	/*
208145516Sdarrenr	 * Get the port#
209145516Sdarrenr	 */
210145516Sdarrenr	for (l = 0; ISDIGIT(c) && (i > 0); i--) {
211145516Sdarrenr		l *= 10;
212145516Sdarrenr		l += c - '0';
213145516Sdarrenr		c = *s++;
214145516Sdarrenr	}
215145516Sdarrenr	if (i < 3)
216145516Sdarrenr		return 0;
217145516Sdarrenr	if (strncmp(s, "\001\r\n", 3))
218145516Sdarrenr		return 0;
219145516Sdarrenr	s += 3;
220145516Sdarrenr	ircp->irc_len = s - buf;
221145516Sdarrenr	ircp->irc_port = l;
222145516Sdarrenr	return 1;
223145516Sdarrenr}
224145516Sdarrenr
225145516Sdarrenr
226255332Scyint
227255332Scyipf_p_irc_new(arg, fin, aps, nat)
228255332Scy	void *arg;
229255332Scy	fr_info_t *fin;
230255332Scy	ap_session_t *aps;
231255332Scy	nat_t *nat;
232145516Sdarrenr{
233145516Sdarrenr	ircinfo_t *irc;
234145516Sdarrenr
235255332Scy	if (fin->fin_v != 4)
236255332Scy		return -1;
237255332Scy
238145516Sdarrenr	KMALLOC(irc, ircinfo_t *);
239145516Sdarrenr	if (irc == NULL)
240145516Sdarrenr		return -1;
241145516Sdarrenr
242145516Sdarrenr	nat = nat;	/* LINT */
243145516Sdarrenr
244145516Sdarrenr	aps->aps_data = irc;
245145516Sdarrenr	aps->aps_psiz = sizeof(ircinfo_t);
246145516Sdarrenr
247145516Sdarrenr	bzero((char *)irc, sizeof(*irc));
248145516Sdarrenr	return 0;
249145516Sdarrenr}
250145516Sdarrenr
251145516Sdarrenr
252255332Scyint
253255332Scyipf_p_irc_send(fin, nat)
254255332Scy	fr_info_t *fin;
255255332Scy	nat_t *nat;
256145516Sdarrenr{
257145516Sdarrenr	char ctcpbuf[IPF_IRCBUFSZ], newbuf[IPF_IRCBUFSZ];
258145516Sdarrenr	tcphdr_t *tcp, tcph, *tcp2 = &tcph;
259145516Sdarrenr	int off, inc = 0, i, dlen;
260255332Scy	ipf_main_softc_t *softc;
261145516Sdarrenr	size_t nlen = 0, olen;
262145516Sdarrenr	struct in_addr swip;
263145516Sdarrenr	u_short a5, sp;
264145516Sdarrenr	ircinfo_t *irc;
265145516Sdarrenr	fr_info_t fi;
266145516Sdarrenr	nat_t *nat2;
267145516Sdarrenr	u_int a1;
268145516Sdarrenr	ip_t *ip;
269145516Sdarrenr	mb_t *m;
270369272Scy#if SOLARIS
271145516Sdarrenr	mb_t *m1;
272145516Sdarrenr#endif
273255332Scy	softc = fin->fin_main_soft;
274145516Sdarrenr
275145516Sdarrenr	m = fin->fin_m;
276145516Sdarrenr	ip = fin->fin_ip;
277145516Sdarrenr	tcp = (tcphdr_t *)fin->fin_dp;
278145516Sdarrenr	bzero(ctcpbuf, sizeof(ctcpbuf));
279145516Sdarrenr	off = (char *)tcp - (char *)ip + (TCP_OFF(tcp) << 2) + fin->fin_ipoff;
280145516Sdarrenr
281145516Sdarrenr	dlen = MSGDSIZE(m) - off;
282145516Sdarrenr	if (dlen <= 0)
283145516Sdarrenr		return 0;
284145516Sdarrenr	COPYDATA(m, off, MIN(sizeof(ctcpbuf), dlen), ctcpbuf);
285145516Sdarrenr
286145516Sdarrenr	if (dlen <= 0)
287145516Sdarrenr		return 0;
288145516Sdarrenr	ctcpbuf[sizeof(ctcpbuf) - 1] = '\0';
289145516Sdarrenr	*newbuf = '\0';
290145516Sdarrenr
291145516Sdarrenr	irc = nat->nat_aps->aps_data;
292255332Scy	if (ipf_p_irc_complete(irc, ctcpbuf, dlen) == 0)
293145516Sdarrenr		return 0;
294145516Sdarrenr
295145516Sdarrenr	/*
296255332Scy	 * check that IP address in the DCC reply is the same as the
297255332Scy	 * sender of the command - prevents use for port scanning.
298145516Sdarrenr	 */
299255332Scy	if (irc->irc_ipnum != ntohl(nat->nat_osrcaddr))
300145516Sdarrenr		return 0;
301145516Sdarrenr
302145516Sdarrenr	a5 = irc->irc_port;
303145516Sdarrenr
304145516Sdarrenr	/*
305145516Sdarrenr	 * Calculate new address parts for the DCC command
306145516Sdarrenr	 */
307145516Sdarrenr	a1 = ntohl(ip->ip_src.s_addr);
308145516Sdarrenr	olen = irc->irc_len;
309145516Sdarrenr	i = irc->irc_addr - ctcpbuf;
310145516Sdarrenr	i++;
311145516Sdarrenr	(void) strncpy(newbuf, ctcpbuf, i);
312145516Sdarrenr	/* DO NOT change these! */
313145516Sdarrenr#if defined(SNPRINTF) && defined(KERNEL)
314145516Sdarrenr	SNPRINTF(newbuf, sizeof(newbuf) - i, "%u %u\001\r\n", a1, a5);
315145516Sdarrenr#else
316145516Sdarrenr	(void) sprintf(newbuf, "%u %u\001\r\n", a1, a5);
317145516Sdarrenr#endif
318145516Sdarrenr
319145516Sdarrenr	nlen = strlen(newbuf);
320145516Sdarrenr	inc = nlen - olen;
321145516Sdarrenr
322255332Scy	if ((inc + fin->fin_plen) > 65535)
323145516Sdarrenr		return 0;
324145516Sdarrenr
325369272Scy#if SOLARIS
326145516Sdarrenr	for (m1 = m; m1->b_cont; m1 = m1->b_cont)
327145516Sdarrenr		;
328145516Sdarrenr	if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
329145516Sdarrenr		mblk_t *nm;
330145516Sdarrenr
331145516Sdarrenr		/* alloc enough to keep same trailer space for lower driver */
332145516Sdarrenr		nm = allocb(nlen, BPRI_MED);
333255332Scy		PANIC((!nm),("ipf_p_irc_out: allocb failed"));
334145516Sdarrenr
335145516Sdarrenr		nm->b_band = m1->b_band;
336145516Sdarrenr		nm->b_wptr += nlen;
337145516Sdarrenr
338145516Sdarrenr		m1->b_wptr -= olen;
339145516Sdarrenr		PANIC((m1->b_wptr < m1->b_rptr),
340255332Scy		      ("ipf_p_irc_out: cannot handle fragmented data block"));
341145516Sdarrenr
342145516Sdarrenr		linkb(m1, nm);
343145516Sdarrenr	} else {
344145516Sdarrenr# if SOLARIS && defined(ICK_VALID)
345145516Sdarrenr		if (m1->b_datap->db_struiolim == m1->b_wptr)
346145516Sdarrenr			m1->b_datap->db_struiolim += inc;
347145516Sdarrenr		m1->b_datap->db_struioflag &= ~STRUIO_IP;
348145516Sdarrenr# endif
349145516Sdarrenr		m1->b_wptr += inc;
350145516Sdarrenr	}
351145516Sdarrenr#else
352145516Sdarrenr	if (inc < 0)
353145516Sdarrenr		m_adj(m, inc);
354145516Sdarrenr	/* the mbuf chain will be extended if necessary by m_copyback() */
355145516Sdarrenr#endif
356145516Sdarrenr	COPYBACK(m, off, nlen, newbuf);
357255332Scy	fin->fin_flx |= FI_DOCKSUM;
358145516Sdarrenr
359145516Sdarrenr	if (inc != 0) {
360369272Scy#if SOLARIS
361145516Sdarrenr		register u_32_t	sum1, sum2;
362145516Sdarrenr
363255332Scy		sum1 = fin->fin_plen;
364255332Scy		sum2 = fin->fin_plen + inc;
365145516Sdarrenr
366145516Sdarrenr		/* Because ~1 == -2, We really need ~1 == -1 */
367145516Sdarrenr		if (sum1 > sum2)
368145516Sdarrenr			sum2--;
369145516Sdarrenr		sum2 -= sum1;
370145516Sdarrenr		sum2 = (sum2 & 0xffff) + (sum2 >> 16);
371145516Sdarrenr
372255332Scy		ipf_fix_outcksum(0, &ip->ip_sum, sum2, 0);
373145516Sdarrenr#endif
374255332Scy		fin->fin_plen += inc;
375255332Scy		ip->ip_len = htons(fin->fin_plen);
376255332Scy		fin->fin_dlen += inc;
377145516Sdarrenr	}
378145516Sdarrenr
379145516Sdarrenr	/*
380145516Sdarrenr	 * Add skeleton NAT entry for connection which will come back the
381145516Sdarrenr	 * other way.
382145516Sdarrenr	 */
383145516Sdarrenr	sp = htons(a5);
384145516Sdarrenr	/*
385145516Sdarrenr	 * Don't allow the PORT command to specify a port < 1024 due to
386145516Sdarrenr	 * security crap.
387145516Sdarrenr	 */
388145516Sdarrenr	if (ntohs(sp) < 1024)
389145516Sdarrenr		return 0;
390145516Sdarrenr
391145516Sdarrenr	/*
392145516Sdarrenr	 * The server may not make the connection back from port 20, but
393145516Sdarrenr	 * it is the most likely so use it here to check for a conflicting
394145516Sdarrenr	 * mapping.
395145516Sdarrenr	 */
396145516Sdarrenr	bcopy((caddr_t)fin, (caddr_t)&fi, sizeof(fi));
397145516Sdarrenr	fi.fin_data[0] = sp;
398145516Sdarrenr	fi.fin_data[1] = fin->fin_data[1];
399255332Scy	nat2 = ipf_nat_outlookup(fin, IPN_TCP, nat->nat_pr[1], nat->nat_nsrcip,
400145516Sdarrenr			     ip->ip_dst);
401145516Sdarrenr	if (nat2 == NULL) {
402255332Scy#ifdef USE_MUTEXES
403255332Scy		ipf_nat_softc_t *softn = softc->ipf_nat_soft;
404255332Scy#endif
405255332Scy
406145516Sdarrenr		bcopy((caddr_t)fin, (caddr_t)&fi, sizeof(fi));
407145516Sdarrenr		bzero((char *)tcp2, sizeof(*tcp2));
408145516Sdarrenr		tcp2->th_win = htons(8192);
409145516Sdarrenr		tcp2->th_sport = sp;
410145516Sdarrenr		tcp2->th_dport = 0; /* XXX - don't specify remote port */
411145516Sdarrenr		fi.fin_data[0] = ntohs(sp);
412145516Sdarrenr		fi.fin_data[1] = 0;
413145516Sdarrenr		fi.fin_dp = (char *)tcp2;
414145516Sdarrenr		fi.fin_fr = &ircnatfr;
415145516Sdarrenr		fi.fin_dlen = sizeof(*tcp2);
416145516Sdarrenr		fi.fin_plen = fi.fin_hlen + sizeof(*tcp2);
417145516Sdarrenr		swip = ip->ip_src;
418255332Scy		ip->ip_src = nat->nat_nsrcip;
419255332Scy		MUTEX_ENTER(&softn->ipf_nat_new);
420255332Scy		nat2 = ipf_nat_add(&fi, nat->nat_ptr, NULL,
421145516Sdarrenr			       NAT_SLAVE|IPN_TCP|SI_W_DPORT, NAT_OUTBOUND);
422255332Scy		MUTEX_EXIT(&softn->ipf_nat_new);
423145516Sdarrenr		if (nat2 != NULL) {
424255332Scy			(void) ipf_nat_proto(&fi, nat2, 0);
425255332Scy			MUTEX_ENTER(&nat2->nat_lock);
426255332Scy			ipf_nat_update(&fi, nat2);
427255332Scy			MUTEX_EXIT(&nat2->nat_lock);
428145516Sdarrenr
429255332Scy			(void) ipf_state_add(softc, &fi, NULL, SI_W_DPORT);
430145516Sdarrenr		}
431145516Sdarrenr		ip->ip_src = swip;
432145516Sdarrenr	}
433145516Sdarrenr	return inc;
434145516Sdarrenr}
435145516Sdarrenr
436145516Sdarrenr
437255332Scyint
438255332Scyipf_p_irc_out(arg, fin, aps, nat)
439255332Scy	void *arg;
440255332Scy	fr_info_t *fin;
441255332Scy	ap_session_t *aps;
442255332Scy	nat_t *nat;
443145516Sdarrenr{
444145516Sdarrenr	aps = aps;	/* LINT */
445255332Scy	return ipf_p_irc_send(fin, nat);
446145516Sdarrenr}
447