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