ip_ftp_pxy.c revision 92685
1/*
2 * Simple FTP transparent proxy for in-kernel use.  For use with the NAT
3 * code.
4 *
5 * $FreeBSD: head/sys/contrib/ipfilter/netinet/ip_ftp_pxy.c 92685 2002-03-19 11:44:16Z darrenr $
6 */
7#if SOLARIS && defined(_KERNEL)
8extern	kmutex_t	ipf_rw;
9#endif
10
11#define	isdigit(x)	((x) >= '0' && (x) <= '9')
12#define	isupper(x)	(((unsigned)(x) >= 'A') && ((unsigned)(x) <= 'Z'))
13#define	islower(x)	(((unsigned)(x) >= 'a') && ((unsigned)(x) <= 'z'))
14#define	isalpha(x)	(isupper(x) || islower(x))
15#define	toupper(x)	(isupper(x) ? (x) : (x) - 'a' + 'A')
16
17#define	IPF_FTP_PROXY
18
19#define	IPF_MINPORTLEN	18
20#define	IPF_MAXPORTLEN	30
21#define	IPF_MIN227LEN	39
22#define	IPF_MAX227LEN	51
23#define	IPF_FTPBUFSZ	96	/* This *MUST* be >= 53! */
24
25#define	FTPXY_GO	0
26#define	FTPXY_INIT	1
27#define	FTPXY_USER_1	2
28#define	FTPXY_USOK_1	3
29#define	FTPXY_PASS_1	4
30#define	FTPXY_PAOK_1	5
31#define	FTPXY_AUTH_1	6
32#define	FTPXY_AUOK_1	7
33#define	FTPXY_ADAT_1	8
34#define	FTPXY_ADOK_1	9
35#define	FTPXY_ACCT_1	10
36#define	FTPXY_ACOK_1	11
37#define	FTPXY_USER_2	12
38#define	FTPXY_USOK_2	13
39#define	FTPXY_PASS_2	14
40#define	FTPXY_PAOK_2	15
41
42int ippr_ftp_client __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int));
43int ippr_ftp_complete __P((char *, size_t));
44int ippr_ftp_in __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *));
45int ippr_ftp_init __P((void));
46int ippr_ftp_new __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *));
47int ippr_ftp_out __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *));
48int ippr_ftp_pasv __P((fr_info_t *, ip_t *, nat_t *, ftpside_t *, int));
49int ippr_ftp_port __P((fr_info_t *, ip_t *, nat_t *, ftpside_t *, int));
50int ippr_ftp_process __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int));
51int ippr_ftp_server __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int));
52int ippr_ftp_valid __P((int, char *, size_t));
53int ippr_ftp_server_valid __P((char *, size_t));
54int ippr_ftp_client_valid __P((char *, size_t));
55u_short ippr_ftp_atoi __P((char **));
56
57static	frentry_t	ftppxyfr;
58int	ippr_ftp_pasvonly = 0;
59int	ippr_ftp_insecure = 0;
60
61
62/*
63 * Initialize local structures.
64 */
65int ippr_ftp_init()
66{
67	bzero((char *)&ftppxyfr, sizeof(ftppxyfr));
68	ftppxyfr.fr_ref = 1;
69	ftppxyfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE;
70	return 0;
71}
72
73
74int ippr_ftp_new(fin, ip, aps, nat)
75fr_info_t *fin;
76ip_t *ip;
77ap_session_t *aps;
78nat_t *nat;
79{
80	ftpinfo_t *ftp;
81	ftpside_t *f;
82
83	KMALLOC(ftp, ftpinfo_t *);
84	if (ftp == NULL)
85		return -1;
86	aps->aps_data = ftp;
87	aps->aps_psiz = sizeof(ftpinfo_t);
88
89	bzero((char *)ftp, sizeof(*ftp));
90	f = &ftp->ftp_side[0];
91	f->ftps_rptr = f->ftps_buf;
92	f->ftps_wptr = f->ftps_buf;
93	f = &ftp->ftp_side[1];
94	f->ftps_rptr = f->ftps_buf;
95	f->ftps_wptr = f->ftps_buf;
96	ftp->ftp_passok = FTPXY_INIT;
97	return 0;
98}
99
100
101int ippr_ftp_port(fin, ip, nat, f, dlen)
102fr_info_t *fin;
103ip_t *ip;
104nat_t *nat;
105ftpside_t *f;
106int dlen;
107{
108	tcphdr_t *tcp, tcph, *tcp2 = &tcph;
109	char newbuf[IPF_FTPBUFSZ], *s;
110	u_int a1, a2, a3, a4;
111	struct in_addr swip;
112	u_short a5, a6, sp;
113	size_t nlen, olen;
114	fr_info_t fi;
115	int inc, off;
116	nat_t *ipn;
117	mb_t *m;
118#if	SOLARIS
119	mb_t *m1;
120#endif
121
122	tcp = (tcphdr_t *)fin->fin_dp;
123	/*
124	 * Check for client sending out PORT message.
125	 */
126	if (dlen < IPF_MINPORTLEN)
127		return 0;
128	off = fin->fin_hlen + (tcp->th_off << 2);
129	/*
130	 * Skip the PORT command + space
131	 */
132	s = f->ftps_rptr + 5;
133	/*
134	 * Pick out the address components, two at a time.
135	 */
136	a1 = ippr_ftp_atoi(&s);
137	if (!s)
138		return 0;
139	a2 = ippr_ftp_atoi(&s);
140	if (!s)
141		return 0;
142	/*
143	 * check that IP address in the PORT/PASV reply is the same as the
144	 * sender of the command - prevents using PORT for port scanning.
145	 */
146	a1 <<= 16;
147	a1 |= a2;
148	if (a1 != ntohl(nat->nat_inip.s_addr))
149		return 0;
150
151	a5 = ippr_ftp_atoi(&s);
152	if (!s)
153		return 0;
154	if (*s == ')')
155		s++;
156
157	/*
158	 * check for CR-LF at the end.
159	 */
160	if (*s == '\n')
161		s--;
162	if ((*s == '\r') && (*(s + 1) == '\n')) {
163		s += 2;
164		a6 = a5 & 0xff;
165	} else
166		return 0;
167	a5 >>= 8;
168	a5 &= 0xff;
169	/*
170	 * Calculate new address parts for PORT command
171	 */
172	a1 = ntohl(ip->ip_src.s_addr);
173	a2 = (a1 >> 16) & 0xff;
174	a3 = (a1 >> 8) & 0xff;
175	a4 = a1 & 0xff;
176	a1 >>= 24;
177	olen = s - f->ftps_rptr;
178	/* DO NOT change this to snprintf! */
179	(void) sprintf(newbuf, "%s %u,%u,%u,%u,%u,%u\r\n",
180		       "PORT", a1, a2, a3, a4, a5, a6);
181
182	nlen = strlen(newbuf);
183	inc = nlen - olen;
184	if ((inc + ip->ip_len) > 65535)
185		return 0;
186
187#if SOLARIS
188	m = fin->fin_qfm;
189	for (m1 = m; m1->b_cont; m1 = m1->b_cont)
190		;
191	if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
192		mblk_t *nm;
193
194		/* alloc enough to keep same trailer space for lower driver */
195		nm = allocb(nlen, BPRI_MED);
196		PANIC((!nm),("ippr_ftp_out: allocb failed"));
197
198		nm->b_band = m1->b_band;
199		nm->b_wptr += nlen;
200
201		m1->b_wptr -= olen;
202		PANIC((m1->b_wptr < m1->b_rptr),
203		      ("ippr_ftp_out: cannot handle fragmented data block"));
204
205		linkb(m1, nm);
206	} else {
207		if (m1->b_datap->db_struiolim == m1->b_wptr)
208			m1->b_datap->db_struiolim += inc;
209		m1->b_datap->db_struioflag &= ~STRUIO_IP;
210		m1->b_wptr += inc;
211	}
212	copyin_mblk(m, off, nlen, newbuf);
213#else
214	m = *((mb_t **)fin->fin_mp);
215	if (inc < 0)
216		m_adj(m, inc);
217	/* the mbuf chain will be extended if necessary by m_copyback() */
218	m_copyback(m, off, nlen, newbuf);
219# ifdef	M_PKTHDR
220	if (!(m->m_flags & M_PKTHDR))
221		m->m_pkthdr.len += inc;
222# endif
223#endif
224	if (inc != 0) {
225#if SOLARIS || defined(__sgi)
226		register u_32_t	sum1, sum2;
227
228		sum1 = ip->ip_len;
229		sum2 = ip->ip_len + inc;
230
231		/* Because ~1 == -2, We really need ~1 == -1 */
232		if (sum1 > sum2)
233			sum2--;
234		sum2 -= sum1;
235		sum2 = (sum2 & 0xffff) + (sum2 >> 16);
236
237		fix_outcksum(fin, &ip->ip_sum, sum2);
238#endif
239		ip->ip_len += inc;
240	}
241
242	/*
243	 * Add skeleton NAT entry for connection which will come back the
244	 * other way.
245	 */
246	sp = (a5 << 8 | a6);
247	/*
248	 * Don't allow the PORT command to specify a port < 1024 due to
249	 * security crap.
250	 */
251	if (sp < 1024)
252		return 0;
253	/*
254	 * The server may not make the connection back from port 20, but
255	 * it is the most likely so use it here to check for a conflicting
256	 * mapping.
257	 */
258	bcopy((char *)fin, (char *)&fi, sizeof(fi));
259	fi.fin_data[0] = sp;
260	fi.fin_data[1] = fin->fin_data[1] - 1;
261	ipn = nat_outlookup(&fi, IPN_TCP, nat->nat_p, nat->nat_inip,
262			    ip->ip_dst, 0);
263	if (ipn == NULL) {
264		int slen;
265
266		slen = ip->ip_len;
267		ip->ip_len = fin->fin_hlen + sizeof(*tcp2);
268		bzero((char *)tcp2, sizeof(*tcp2));
269		tcp2->th_win = htons(8192);
270		tcp2->th_sport = htons(sp);
271		tcp2->th_off = 5;
272		tcp2->th_dport = 0; /* XXX - don't specify remote port */
273		fi.fin_data[1] = 0;
274		fi.fin_dlen = sizeof(*tcp2);
275		fi.fin_dp = (char *)tcp2;
276		fi.fin_fr = &ftppxyfr;
277		fi.fin_out = 1;
278		swip = ip->ip_src;
279		fi.fin_fi.fi_saddr = nat->nat_inip.s_addr;
280		ip->ip_src = nat->nat_inip;
281		ipn = nat_new(&fi, ip, nat->nat_ptr, NULL, IPN_TCP|FI_W_DPORT,
282			      NAT_OUTBOUND);
283		if (ipn != NULL) {
284			ipn->nat_age = fr_defnatage;
285			(void) fr_addstate(ip, &fi, NULL,
286					   FI_W_DPORT|FI_IGNOREPKT);
287		}
288		ip->ip_len = slen;
289		ip->ip_src = swip;
290	}
291	return APR_INC(inc);
292}
293
294
295int ippr_ftp_client(fin, ip, nat, ftp, dlen)
296fr_info_t *fin;
297nat_t *nat;
298ftpinfo_t *ftp;
299ip_t *ip;
300int dlen;
301{
302	char *rptr, *wptr, cmd[6], c;
303	ftpside_t *f;
304	int inc, i;
305
306	inc = 0;
307	f = &ftp->ftp_side[0];
308	rptr = f->ftps_rptr;
309	wptr = f->ftps_wptr;
310
311	for (i = 0; (i < 5) && (i < dlen); i++) {
312		c = rptr[i];
313		if (isalpha(c)) {
314			cmd[i] = toupper(c);
315		} else {
316			cmd[i] = c;
317		}
318	}
319	cmd[i] = '\0';
320
321	ftp->ftp_incok = 0;
322	if (!strncmp(cmd, "USER ", 5) || !strncmp(cmd, "XAUT ", 5)) {
323		if (ftp->ftp_passok == FTPXY_ADOK_1 ||
324		    ftp->ftp_passok == FTPXY_AUOK_1) {
325			ftp->ftp_passok = FTPXY_USER_2;
326			ftp->ftp_incok = 1;
327		} else {
328			ftp->ftp_passok = FTPXY_USER_1;
329			ftp->ftp_incok = 1;
330		}
331	} else if (!strncmp(cmd, "AUTH ", 5)) {
332		ftp->ftp_passok = FTPXY_AUTH_1;
333		ftp->ftp_incok = 1;
334	} else if (!strncmp(cmd, "PASS ", 5)) {
335		if (ftp->ftp_passok == FTPXY_USOK_1) {
336			ftp->ftp_passok = FTPXY_PASS_1;
337			ftp->ftp_incok = 1;
338		} else if (ftp->ftp_passok == FTPXY_USOK_2) {
339			ftp->ftp_passok = FTPXY_PASS_2;
340			ftp->ftp_incok = 1;
341		}
342	} else if ((ftp->ftp_passok == FTPXY_AUOK_1) &&
343		   !strncmp(cmd, "ADAT ", 5)) {
344		ftp->ftp_passok = FTPXY_ADAT_1;
345		ftp->ftp_incok = 1;
346	} else if ((ftp->ftp_passok == FTPXY_PAOK_1 ||
347		    ftp->ftp_passok == FTPXY_PAOK_2) &&
348		 !strncmp(cmd, "ACCT ", 5)) {
349		ftp->ftp_passok = FTPXY_ACCT_1;
350		ftp->ftp_incok = 1;
351	} else if ((ftp->ftp_passok == FTPXY_GO) && !ippr_ftp_pasvonly &&
352		 !strncmp(cmd, "PORT ", 5)) {
353		inc = ippr_ftp_port(fin, ip, nat, f, dlen);
354	} else if (ippr_ftp_insecure && !ippr_ftp_pasvonly &&
355		   !strncmp(cmd, "PORT ", 5)) {
356		inc = ippr_ftp_port(fin, ip, nat, f, dlen);
357	}
358
359	while ((*rptr++ != '\n') && (rptr < wptr))
360		;
361	f->ftps_rptr = rptr;
362	return inc;
363}
364
365
366int ippr_ftp_pasv(fin, ip, nat, f, dlen)
367fr_info_t *fin;
368ip_t *ip;
369nat_t *nat;
370ftpside_t *f;
371int dlen;
372{
373	tcphdr_t *tcp, tcph, *tcp2 = &tcph;
374	struct in_addr swip, swip2;
375	u_int a1, a2, a3, a4;
376	u_short a5, a6, dp;
377	fr_info_t fi;
378	nat_t *ipn;
379	int inc;
380	char *s;
381
382#define	PASV_REPLEN	24
383	/*
384	 * Check for PASV reply message.
385	 */
386	if (dlen < IPF_MIN227LEN)
387		return 0;
388	else if (strncmp(f->ftps_rptr, "227 Entering Passive Mod", PASV_REPLEN))
389		return 0;
390
391	tcp = (tcphdr_t *)fin->fin_dp;
392
393	/*
394	 * Skip the PORT command + space
395	 */
396	s = f->ftps_rptr + PASV_REPLEN;
397	while (*s && !isdigit(*s))
398		s++;
399	/*
400	 * Pick out the address components, two at a time.
401	 */
402	a1 = ippr_ftp_atoi(&s);
403	if (!s)
404		return 0;
405	a2 = ippr_ftp_atoi(&s);
406	if (!s)
407		return 0;
408
409	/*
410	 * check that IP address in the PORT/PASV reply is the same as the
411	 * sender of the command - prevents using PORT for port scanning.
412	 */
413	a1 <<= 16;
414	a1 |= a2;
415	if (a1 != ntohl(nat->nat_oip.s_addr))
416		return 0;
417
418	a5 = ippr_ftp_atoi(&s);
419	if (!s)
420		return 0;
421
422	if (*s == ')')
423		s++;
424	if (*s == '.')
425		s++;
426	if (*s == '\n')
427		s--;
428	/*
429	 * check for CR-LF at the end.
430	 */
431	if ((*s == '\r') && (*(s + 1) == '\n')) {
432		s += 2;
433		a6 = a5 & 0xff;
434	} else
435		return 0;
436	a5 >>= 8;
437	/*
438	 * Calculate new address parts for 227 reply
439	 */
440	a1 = ntohl(ip->ip_src.s_addr);
441	a2 = (a1 >> 16) & 0xff;
442	a3 = (a1 >> 8) & 0xff;
443	a4 = a1 & 0xff;
444	a1 >>= 24;
445	inc = 0;
446#if 0
447	olen = s - f->ftps_rptr;
448	(void) sprintf(newbuf, "%s %u,%u,%u,%u,%u,%u\r\n",
449		       "227 Entering Passive Mode", a1, a2, a3, a4, a5, a6);
450	nlen = strlen(newbuf);
451	inc = nlen - olen;
452	if ((inc + ip->ip_len) > 65535)
453		return 0;
454
455#if SOLARIS
456	m = fin->fin_qfm;
457	for (m1 = m; m1->b_cont; m1 = m1->b_cont)
458		;
459	if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
460		mblk_t *nm;
461
462		/* alloc enough to keep same trailer space for lower driver */
463		nm = allocb(nlen, BPRI_MED);
464		PANIC((!nm),("ippr_ftp_out: allocb failed"));
465
466		nm->b_band = m1->b_band;
467		nm->b_wptr += nlen;
468
469		m1->b_wptr -= olen;
470		PANIC((m1->b_wptr < m1->b_rptr),
471		      ("ippr_ftp_out: cannot handle fragmented data block"));
472
473		linkb(m1, nm);
474	} else {
475		m1->b_wptr += inc;
476	}
477	/*copyin_mblk(m, off, nlen, newbuf);*/
478#else /* SOLARIS */
479	m = *((mb_t **)fin->fin_mp);
480	if (inc < 0)
481		m_adj(m, inc);
482	/* the mbuf chain will be extended if necessary by m_copyback() */
483	/*m_copyback(m, off, nlen, newbuf);*/
484#endif /* SOLARIS */
485	if (inc != 0) {
486#if SOLARIS || defined(__sgi)
487		register u_32_t	sum1, sum2;
488
489		sum1 = ip->ip_len;
490		sum2 = ip->ip_len + inc;
491
492		/* Because ~1 == -2, We really need ~1 == -1 */
493		if (sum1 > sum2)
494			sum2--;
495		sum2 -= sum1;
496		sum2 = (sum2 & 0xffff) + (sum2 >> 16);
497
498		fix_outcksum(fin, &ip->ip_sum, sum2);
499#endif /* SOLARIS || defined(__sgi) */
500		ip->ip_len += inc;
501	}
502#endif /* 0 */
503
504	/*
505	 * Add skeleton NAT entry for connection which will come back the
506	 * other way.
507	 */
508	bcopy((char *)fin, (char *)&fi, sizeof(fi));
509	fi.fin_data[0] = 0;
510	dp = htons(fin->fin_data[1] - 1);
511	fi.fin_data[1] = ntohs(dp);
512	ipn = nat_outlookup(&fi, IPN_TCP, nat->nat_p, nat->nat_inip,
513			    ip->ip_dst, 0);
514	if (ipn == NULL) {
515		int slen;
516
517		slen = ip->ip_len;
518		ip->ip_len = fin->fin_hlen + sizeof(*tcp2);
519		bzero((char *)tcp2, sizeof(*tcp2));
520		tcp2->th_win = htons(8192);
521		tcp2->th_sport = 0;		/* XXX - fake it for nat_new */
522		tcp2->th_off = 5;
523		fi.fin_data[1] = a5 << 8 | a6;
524		fi.fin_dlen = sizeof(*tcp2);
525		tcp2->th_dport = htons(fi.fin_data[1]);
526		fi.fin_data[0] = 0;
527		fi.fin_dp = (char *)tcp2;
528		fi.fin_fr = &ftppxyfr;
529		fi.fin_out = 1;
530		swip = ip->ip_src;
531		swip2 = ip->ip_dst;
532		fi.fin_fi.fi_daddr = ip->ip_src.s_addr;
533		fi.fin_fi.fi_saddr = nat->nat_inip.s_addr;
534		ip->ip_dst = ip->ip_src;
535		ip->ip_src = nat->nat_inip;
536		ipn = nat_new(&fi, ip, nat->nat_ptr, NULL, IPN_TCP|FI_W_SPORT,
537			      NAT_OUTBOUND);
538		if (ipn != NULL) {
539			ipn->nat_age = fr_defnatage;
540			(void) fr_addstate(ip, &fi, NULL,
541					   FI_W_SPORT|FI_IGNOREPKT);
542		}
543		ip->ip_len = slen;
544		ip->ip_src = swip;
545		ip->ip_dst = swip2;
546	}
547	return inc;
548}
549
550
551int ippr_ftp_server(fin, ip, nat, ftp, dlen)
552fr_info_t *fin;
553ip_t *ip;
554nat_t *nat;
555ftpinfo_t *ftp;
556int dlen;
557{
558	char *rptr, *wptr;
559	ftpside_t *f;
560	int inc;
561
562	inc = 0;
563	f = &ftp->ftp_side[1];
564	rptr = f->ftps_rptr;
565	wptr = f->ftps_wptr;
566
567	if (!isdigit(*rptr) || !isdigit(*(rptr + 1)) || !isdigit(*(rptr + 2)))
568		return inc;
569	if (ftp->ftp_passok == FTPXY_GO) {
570		if (!strncmp(rptr, "227 ", 4))
571			inc = ippr_ftp_pasv(fin, ip, nat, f, dlen);
572	} else if (ippr_ftp_insecure && !strncmp(rptr, "227 ", 4)) {
573		inc = ippr_ftp_pasv(fin, ip, nat, f, dlen);
574	} else if (*rptr == '5' || *rptr == '4')
575		ftp->ftp_passok = FTPXY_INIT;
576	else if (ftp->ftp_incok) {
577		if (*rptr == '3') {
578			if (ftp->ftp_passok == FTPXY_ACCT_1)
579				ftp->ftp_passok = FTPXY_GO;
580			else
581				ftp->ftp_passok++;
582		} else if (*rptr == '2') {
583			switch (ftp->ftp_passok)
584			{
585			case FTPXY_USER_1 :
586			case FTPXY_USER_2 :
587			case FTPXY_PASS_1 :
588			case FTPXY_PASS_2 :
589			case FTPXY_ACCT_1 :
590				ftp->ftp_passok = FTPXY_GO;
591				break;
592			default :
593				ftp->ftp_passok += 3;
594				break;
595			}
596		}
597	}
598	ftp->ftp_incok = 0;
599	while ((*rptr++ != '\n') && (rptr < wptr))
600		;
601	f->ftps_rptr = rptr;
602	return inc;
603}
604
605
606/*
607 * Look to see if the buffer starts with something which we recognise as
608 * being the correct syntax for the FTP protocol.
609 */
610int ippr_ftp_client_valid(buf, len)
611char *buf;
612size_t len;
613{
614	register char *s, c;
615	register size_t i = len;
616
617	if (i < 5)
618		return 2;
619	s = buf;
620	c = *s++;
621	i--;
622
623	if (isalpha(c)) {
624		c = *s++;
625		i--;
626		if (isalpha(c)) {
627			c = *s++;
628			i--;
629			if (isalpha(c)) {
630				c = *s++;
631				i--;
632				if (isalpha(c)) {
633					c = *s++;
634					i--;
635					if ((c != ' ') && (c != '\r'))
636						return 1;
637				} else if ((c != ' ') && (c != '\r'))
638					return 1;
639			} else
640				return 1;
641		} else
642			return 1;
643	} else
644		return 1;
645	for (; i; i--) {
646		c = *s++;
647		if (c == '\n')
648			return 0;
649	}
650	return 2;
651}
652
653
654int ippr_ftp_server_valid(buf, len)
655char *buf;
656size_t len;
657{
658	register char *s, c;
659	register size_t i = len;
660
661	if (i < 5)
662		return 2;
663	s = buf;
664	c = *s++;
665	i--;
666
667	if (isdigit(c)) {
668		c = *s++;
669		i--;
670		if (isdigit(c)) {
671			c = *s++;
672			i--;
673			if (isdigit(c)) {
674				c = *s++;
675				i--;
676				if ((c != '-') && (c != ' '))
677					return 1;
678			} else
679				return 1;
680		} else
681			return 1;
682	} else
683		return 1;
684	for (; i; i--) {
685		c = *s++;
686		if (c == '\n')
687			return 0;
688	}
689	return 2;
690}
691
692
693int ippr_ftp_valid(side, buf, len)
694int side;
695char *buf;
696size_t len;
697{
698	int ret;
699
700	if (side == 0)
701		ret = ippr_ftp_client_valid(buf, len);
702	else
703		ret = ippr_ftp_server_valid(buf, len);
704	return ret;
705}
706
707
708int ippr_ftp_process(fin, ip, nat, ftp, rv)
709fr_info_t *fin;
710ip_t *ip;
711nat_t *nat;
712ftpinfo_t *ftp;
713int rv;
714{
715	int mlen, len, off, inc, i, sel;
716	char *rptr, *wptr;
717	ftpside_t *f, *t;
718	tcphdr_t *tcp;
719	mb_t *m;
720
721	tcp = (tcphdr_t *)fin->fin_dp;
722	off = fin->fin_hlen + (tcp->th_off << 2);
723
724#if	SOLARIS
725	m = fin->fin_qfm;
726#else
727	m = *((mb_t **)fin->fin_mp);
728#endif
729
730#if	SOLARIS
731	mlen = msgdsize(m) - off;
732#else
733	mlen = mbufchainlen(m) - off;
734#endif
735
736	t = &ftp->ftp_side[1 - rv];
737	f = &ftp->ftp_side[rv];
738	if (!mlen) {
739		if (!t->ftps_seq ||
740		    (int)ntohl(tcp->th_ack) - (int)t->ftps_seq > 0)
741			t->ftps_seq = ntohl(tcp->th_ack);
742		f->ftps_len = 0;
743		return 0;
744	}
745
746	inc = 0;
747	rptr = f->ftps_rptr;
748	wptr = f->ftps_wptr;
749
750	sel = nat->nat_aps->aps_sel[1 - rv];
751	if (rv)
752		i = nat->nat_aps->aps_ackoff[sel];
753	else
754		i = nat->nat_aps->aps_seqoff[sel];
755	/*
756	 * XXX - Ideally, this packet should get dropped because we now know
757	 * that it is out of order (and there is no real danger in doing so
758	 * apart from causing packets to go through here ordered).
759	 */
760	if (f->ftps_len + f->ftps_seq == ntohl(tcp->th_seq))
761		f->ftps_seq = ntohl(tcp->th_seq);
762	else if (ntohl(tcp->th_seq) + i != f->ftps_seq) {
763		return APR_ERR(1);
764	}
765	f->ftps_len = mlen;
766
767	while (mlen > 0) {
768		len = MIN(mlen, FTP_BUFSZ / 2);
769
770#if	SOLARIS
771		copyout_mblk(m, off, len, wptr);
772#else
773		m_copydata(m, off, len, wptr);
774#endif
775		mlen -= len;
776		off += len;
777		wptr += len;
778		f->ftps_wptr = wptr;
779		if (f->ftps_junk == 2)
780			f->ftps_junk = ippr_ftp_valid(rv, rptr, wptr - rptr);
781
782		while ((f->ftps_junk == 0) && (wptr > rptr)) {
783			f->ftps_junk = ippr_ftp_valid(rv, rptr, wptr - rptr);
784			if (f->ftps_junk == 0) {
785				f->ftps_cmds++;
786				len = wptr - rptr;
787				f->ftps_rptr = rptr;
788				if (rv)
789					inc += ippr_ftp_server(fin, ip, nat,
790							       ftp, len);
791				else
792					inc += ippr_ftp_client(fin, ip, nat,
793							       ftp, len);
794				rptr = f->ftps_rptr;
795				wptr = f->ftps_wptr;
796			}
797		}
798
799		/*
800		 * Off to a bad start so lets just forget about using the
801		 * ftp proxy for this connection.
802		 */
803		if ((f->ftps_cmds == 0) && (f->ftps_junk == 1))
804			return APR_ERR(2);
805
806		while ((f->ftps_junk == 1) && (rptr < wptr)) {
807			while ((rptr < wptr) && (*rptr != '\r'))
808				rptr++;
809
810			if (*rptr == '\r') {
811				if (rptr + 1 < wptr) {
812					if (*(rptr + 1) == '\n') {
813						rptr += 2;
814						f->ftps_junk = 0;
815					} else
816						rptr++;
817				} else
818					break;
819			}
820		}
821		f->ftps_rptr = rptr;
822
823		if (rptr == wptr) {
824			rptr = wptr = f->ftps_buf;
825		} else {
826			if ((wptr > f->ftps_buf + FTP_BUFSZ / 2)) {
827				i = wptr - rptr;
828				if ((rptr == f->ftps_buf) ||
829				    (wptr - rptr > FTP_BUFSZ / 2)) {
830					f->ftps_junk = 1;
831					rptr = wptr = f->ftps_buf;
832				} else {
833					bcopy(rptr, f->ftps_buf, i);
834					wptr = f->ftps_buf + i;
835					rptr = f->ftps_buf;
836				}
837			}
838			f->ftps_rptr = rptr;
839			f->ftps_wptr = wptr;
840		}
841	}
842
843	t->ftps_seq = ntohl(tcp->th_ack);
844	f->ftps_rptr = rptr;
845	f->ftps_wptr = wptr;
846	return APR_INC(inc);
847}
848
849
850int ippr_ftp_out(fin, ip, aps, nat)
851fr_info_t *fin;
852ip_t *ip;
853ap_session_t *aps;
854nat_t *nat;
855{
856	ftpinfo_t *ftp;
857
858	ftp = aps->aps_data;
859	if (ftp == NULL)
860		return 0;
861	return ippr_ftp_process(fin, ip, nat, ftp, 0);
862}
863
864
865int ippr_ftp_in(fin, ip, aps, nat)
866fr_info_t *fin;
867ip_t *ip;
868ap_session_t *aps;
869nat_t *nat;
870{
871	ftpinfo_t *ftp;
872
873	ftp = aps->aps_data;
874	if (ftp == NULL)
875		return 0;
876	return ippr_ftp_process(fin, ip, nat, ftp, 1);
877}
878
879
880/*
881 * ippr_ftp_atoi - implement a version of atoi which processes numbers in
882 * pairs separated by commas (which are expected to be in the range 0 - 255),
883 * returning a 16 bit number combining either side of the , as the MSB and
884 * LSB.
885 */
886u_short ippr_ftp_atoi(ptr)
887char **ptr;
888{
889	register char *s = *ptr, c;
890	register u_char i = 0, j = 0;
891
892	while ((c = *s++) && isdigit(c)) {
893		i *= 10;
894		i += c - '0';
895	}
896	if (c != ',') {
897		*ptr = NULL;
898		return 0;
899	}
900	while ((c = *s++) && isdigit(c)) {
901		j *= 10;
902		j += c - '0';
903	}
904	*ptr = s;
905	i &= 0xff;
906	j &= 0xff;
907	return (i << 8) | j;
908}
909