2 * Simple FTP transparent proxy for in-kernel use. For use with the NAT
7 #if SOLARIS && defined(_KERNEL)
8 extern kmutex_t ipf_rw;
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')
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! */
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
42 int ippr_ftp_client __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int));
43 int ippr_ftp_complete __P((char *, size_t));
44 int ippr_ftp_in __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *));
45 int ippr_ftp_init __P((void));
46 int ippr_ftp_new __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *));
47 int ippr_ftp_out __P((fr_info_t *, ip_t *, ap_session_t *, nat_t *));
48 int ippr_ftp_pasv __P((fr_info_t *, ip_t *, nat_t *, ftpside_t *, int));
49 int ippr_ftp_port __P((fr_info_t *, ip_t *, nat_t *, ftpside_t *, int));
50 int ippr_ftp_process __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int));
51 int ippr_ftp_server __P((fr_info_t *, ip_t *, nat_t *, ftpinfo_t *, int));
52 int ippr_ftp_valid __P((char *, size_t));
53 u_short ippr_ftp_atoi __P((char **));
55 static frentry_t natfr;
56 int ippr_ftp_pasvonly = 0;
57 int ippr_ftp_insecure = 0;
61 * Initialize local structures.
65 bzero((char *)&natfr, sizeof(natfr));
67 natfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE;
72 int ippr_ftp_new(fin, ip, aps, nat)
81 KMALLOC(ftp, ftpinfo_t *);
85 aps->aps_psiz = sizeof(ftpinfo_t);
87 bzero((char *)ftp, sizeof(*ftp));
88 f = &ftp->ftp_side[0];
89 f->ftps_rptr = f->ftps_buf;
90 f->ftps_wptr = f->ftps_buf;
91 f = &ftp->ftp_side[1];
92 f->ftps_rptr = f->ftps_buf;
93 f->ftps_wptr = f->ftps_buf;
94 ftp->ftp_passok = FTPXY_INIT;
99 int ippr_ftp_port(fin, ip, nat, f, dlen)
106 tcphdr_t *tcp, tcph, *tcp2 = &tcph;
107 char newbuf[IPF_FTPBUFSZ], *s;
108 u_short a5, a6, sp, dp;
109 u_int a1, a2, a3, a4;
120 tcp = (tcphdr_t *)fin->fin_dp;
122 * Check for client sending out PORT message.
124 if (dlen < IPF_MINPORTLEN)
126 off = fin->fin_hlen + (tcp->th_off << 2);
128 * Skip the PORT command + space
130 s = f->ftps_rptr + 5;
132 * Pick out the address components, two at a time.
134 a1 = ippr_ftp_atoi(&s);
137 a2 = ippr_ftp_atoi(&s);
141 * check that IP address in the PORT/PASV reply is the same as the
142 * sender of the command - prevents using PORT for port scanning.
146 if (a1 != ntohl(nat->nat_inip.s_addr))
149 a5 = ippr_ftp_atoi(&s);
156 * check for CR-LF at the end.
160 if ((*s == '\r') && (*(s + 1) == '\n')) {
168 * Calculate new address parts for PORT command
170 a1 = ntohl(ip->ip_src.s_addr);
171 a2 = (a1 >> 16) & 0xff;
172 a3 = (a1 >> 8) & 0xff;
175 olen = s - f->ftps_rptr;
176 /* DO NOT change this to sprintf! */
177 (void) sprintf(newbuf, "%s %u,%u,%u,%u,%u,%u\r\n",
178 "PORT", a1, a2, a3, a4, a5, a6);
180 nlen = strlen(newbuf);
182 if ((inc + ip->ip_len) > 65535)
187 for (m1 = m; m1->b_cont; m1 = m1->b_cont)
189 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
192 /* alloc enough to keep same trailer space for lower driver */
193 nm = allocb(nlen, BPRI_MED);
194 PANIC((!nm),("ippr_ftp_out: allocb failed"));
196 nm->b_band = m1->b_band;
200 PANIC((m1->b_wptr < m1->b_rptr),
201 ("ippr_ftp_out: cannot handle fragmented data block"));
205 if (m1->b_datap->db_struiolim == m1->b_wptr)
206 m1->b_datap->db_struiolim += inc;
207 m1->b_datap->db_struioflag &= ~STRUIO_IP;
210 copyin_mblk(m, off, nlen, newbuf);
212 m = *((mb_t **)fin->fin_mp);
215 /* the mbuf chain will be extended if necessary by m_copyback() */
216 m_copyback(m, off, nlen, newbuf);
218 if (!(m->m_flags & M_PKTHDR))
219 m->m_pkthdr.len += inc;
223 #if SOLARIS || defined(__sgi)
224 register u_32_t sum1, sum2;
227 sum2 = ip->ip_len + inc;
229 /* Because ~1 == -2, We really need ~1 == -1 */
233 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
235 fix_outcksum(fin, &ip->ip_sum, sum2);
241 * Add skeleton NAT entry for connection which will come back the
244 sp = htons(a5 << 8 | a6);
246 * Don't allow the PORT command to specify a port < 1024 due to
249 if (ntohs(sp) < 1024)
252 * The server may not make the connection back from port 20, but
253 * it is the most likely so use it here to check for a conflicting
256 dp = htons(fin->fin_data[1] - 1);
257 ipn = nat_outlookup(fin->fin_ifp, IPN_TCP, nat->nat_p, nat->nat_inip,
258 ip->ip_dst, (dp << 16) | sp, 0);
263 ip->ip_len = fin->fin_hlen + sizeof(*tcp2);
264 bcopy((char *)fin, (char *)&fi, sizeof(fi));
265 bzero((char *)tcp2, sizeof(*tcp2));
266 tcp2->th_win = htons(8192);
269 tcp2->th_dport = 0; /* XXX - don't specify remote port */
270 fi.fin_data[0] = ntohs(sp);
272 fi.fin_dlen = sizeof(*tcp2);
273 fi.fin_dp = (char *)tcp2;
277 fi.fin_fi.fi_saddr = nat->nat_inip.s_addr;
278 ip->ip_src = nat->nat_inip;
279 ipn = nat_new(nat->nat_ptr, ip, &fi, IPN_TCP|FI_W_DPORT,
282 ipn->nat_age = fr_defnatage;
283 (void) fr_addstate(ip, &fi, FI_W_DPORT);
292 int ippr_ftp_client(fin, ip, nat, ftp, dlen)
299 char *rptr, *wptr, cmd[6], c;
304 f = &ftp->ftp_side[0];
308 for (i = 0; (i < 5) && (i < dlen); i++) {
319 if (!strncmp(cmd, "USER ", 5) || !strncmp(cmd, "XAUT ", 5)) {
320 if (ftp->ftp_passok == FTPXY_ADOK_1 ||
321 ftp->ftp_passok == FTPXY_AUOK_1) {
322 ftp->ftp_passok = FTPXY_USER_2;
325 ftp->ftp_passok = FTPXY_USER_1;
328 } else if (!strncmp(cmd, "AUTH ", 5)) {
329 ftp->ftp_passok = FTPXY_AUTH_1;
331 } else if (!strncmp(cmd, "PASS ", 5)) {
332 if (ftp->ftp_passok == FTPXY_USOK_1) {
333 ftp->ftp_passok = FTPXY_PASS_1;
335 } else if (ftp->ftp_passok == FTPXY_USOK_2) {
336 ftp->ftp_passok = FTPXY_PASS_2;
339 } else if ((ftp->ftp_passok == FTPXY_AUOK_1) &&
340 !strncmp(cmd, "ADAT ", 5)) {
341 ftp->ftp_passok = FTPXY_ADAT_1;
343 } else if ((ftp->ftp_passok == FTPXY_PAOK_2) &&
344 !strncmp(cmd, "ACCT ", 5)) {
345 ftp->ftp_passok = FTPXY_ACCT_1;
347 } else if ((ftp->ftp_passok == FTPXY_GO) && !ippr_ftp_pasvonly &&
348 !strncmp(cmd, "PORT ", 5)) {
349 inc = ippr_ftp_port(fin, ip, nat, f, dlen);
350 } else if (ippr_ftp_insecure && !ippr_ftp_pasvonly &&
351 !strncmp(cmd, "PORT ", 5)) {
352 inc = ippr_ftp_port(fin, ip, nat, f, dlen);
355 while ((*rptr++ != '\n') && (rptr < wptr))
362 int ippr_ftp_pasv(fin, ip, nat, f, dlen)
369 tcphdr_t *tcp, tcph, *tcp2 = &tcph;
370 struct in_addr swip, swip2;
371 u_short a5, a6, sp, dp;
372 u_int a1, a2, a3, a4;
378 #define PASV_REPLEN 24
380 * Check for PASV reply message.
382 if (dlen < IPF_MIN227LEN)
384 else if (strncmp(f->ftps_rptr, "227 Entering Passive Mod", PASV_REPLEN))
387 tcp = (tcphdr_t *)fin->fin_dp;
390 * Skip the PORT command + space
392 s = f->ftps_rptr + PASV_REPLEN;
393 while (*s && !isdigit(*s))
396 * Pick out the address components, two at a time.
398 a1 = ippr_ftp_atoi(&s);
401 a2 = ippr_ftp_atoi(&s);
406 * check that IP address in the PORT/PASV reply is the same as the
407 * sender of the command - prevents using PORT for port scanning.
411 if (a1 != ntohl(nat->nat_oip.s_addr))
414 a5 = ippr_ftp_atoi(&s);
425 * check for CR-LF at the end.
427 if ((*s == '\r') && (*(s + 1) == '\n')) {
434 * Calculate new address parts for 227 reply
436 a1 = ntohl(ip->ip_src.s_addr);
437 a2 = (a1 >> 16) & 0xff;
438 a3 = (a1 >> 8) & 0xff;
443 olen = s - f->ftps_rptr;
444 (void) sprintf(newbuf, "%s %u,%u,%u,%u,%u,%u\r\n",
445 "227 Entering Passive Mode", a1, a2, a3, a4, a5, a6);
446 nlen = strlen(newbuf);
448 if ((inc + ip->ip_len) > 65535)
453 for (m1 = m; m1->b_cont; m1 = m1->b_cont)
455 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
458 /* alloc enough to keep same trailer space for lower driver */
459 nm = allocb(nlen, BPRI_MED);
460 PANIC((!nm),("ippr_ftp_out: allocb failed"));
462 nm->b_band = m1->b_band;
466 PANIC((m1->b_wptr < m1->b_rptr),
467 ("ippr_ftp_out: cannot handle fragmented data block"));
473 /*copyin_mblk(m, off, nlen, newbuf);*/
475 m = *((mb_t **)fin->fin_mp);
478 /* the mbuf chain will be extended if necessary by m_copyback() */
479 /*m_copyback(m, off, nlen, newbuf);*/
482 #if SOLARIS || defined(__sgi)
483 register u_32_t sum1, sum2;
486 sum2 = ip->ip_len + inc;
488 /* Because ~1 == -2, We really need ~1 == -1 */
492 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
494 fix_outcksum(fin, &ip->ip_sum, sum2);
495 #endif /* SOLARIS || defined(__sgi) */
501 * Add skeleton NAT entry for connection which will come back the
505 dp = htons(fin->fin_data[1] - 1);
506 ipn = nat_outlookup(fin->fin_ifp, IPN_TCP, nat->nat_p, nat->nat_inip,
507 ip->ip_dst, (dp << 16) | sp, 0);
512 ip->ip_len = fin->fin_hlen + sizeof(*tcp2);
513 bcopy((char *)fin, (char *)&fi, sizeof(fi));
514 bzero((char *)tcp2, sizeof(*tcp2));
515 tcp2->th_win = htons(8192);
516 tcp2->th_sport = 0; /* XXX - fake it for nat_new */
518 fi.fin_data[0] = a5 << 8 | a6;
519 fi.fin_dlen = sizeof(*tcp2);
520 tcp2->th_dport = htons(fi.fin_data[0]);
522 fi.fin_dp = (char *)tcp2;
527 fi.fin_fi.fi_daddr = ip->ip_src.s_addr;
528 fi.fin_fi.fi_saddr = nat->nat_inip.s_addr;
529 ip->ip_dst = ip->ip_src;
530 ip->ip_src = nat->nat_inip;
531 ipn = nat_new(nat->nat_ptr, ip, &fi, IPN_TCP|FI_W_SPORT,
534 ipn->nat_age = fr_defnatage;
535 (void) fr_addstate(ip, &fi, FI_W_SPORT);
545 int ippr_ftp_server(fin, ip, nat, ftp, dlen)
557 f = &ftp->ftp_side[1];
561 if (!isdigit(*rptr) || !isdigit(*(rptr + 1)) || !isdigit(*(rptr + 2)))
563 if (ftp->ftp_passok == FTPXY_GO) {
564 if (!strncmp(rptr, "227 ", 4))
565 inc = ippr_ftp_pasv(fin, ip, nat, f, dlen);
566 } else if (ippr_ftp_insecure && !strncmp(rptr, "227 ", 4)) {
567 inc = ippr_ftp_pasv(fin, ip, nat, f, dlen);
568 } else if (*rptr == '5' || *rptr == '4')
569 ftp->ftp_passok = FTPXY_INIT;
570 else if (ftp->ftp_incok) {
572 if (ftp->ftp_passok == FTPXY_ACCT_1)
573 ftp->ftp_passok = FTPXY_GO;
576 } else if (*rptr == '2') {
577 switch (ftp->ftp_passok)
584 ftp->ftp_passok = FTPXY_GO;
587 ftp->ftp_passok += 3;
593 while ((*rptr++ != '\n') && (rptr < wptr))
601 * Look to see if the buffer starts with something which we recognise as
602 * being the correct syntax for the FTP protocol.
604 int ippr_ftp_valid(buf, len)
609 register size_t i = len;
626 if ((c != '-') && (c != ' '))
632 } else if (isalpha(c)) {
644 if ((c != ' ') && (c != '\r'))
646 } else if ((c != ' ') && (c != '\r'))
663 int ippr_ftp_process(fin, ip, nat, ftp, rv)
670 int mlen, len, off, inc, i, sel;
676 tcp = (tcphdr_t *)fin->fin_dp;
677 off = fin->fin_hlen + (tcp->th_off << 2);
682 m = *((mb_t **)fin->fin_mp);
686 mlen = msgdsize(m) - off;
688 mlen = mbufchainlen(m) - off;
691 t = &ftp->ftp_side[1 - rv];
692 f = &ftp->ftp_side[rv];
695 (int)ntohl(tcp->th_ack) - (int)t->ftps_seq > 0)
696 t->ftps_seq = ntohl(tcp->th_ack);
705 sel = nat->nat_aps->aps_sel[1 - rv];
707 i = nat->nat_aps->aps_ackoff[sel];
709 i = nat->nat_aps->aps_seqoff[sel];
711 * XXX - Ideally, this packet should get dropped because we now know
712 * that it is out of order (and there is no real danger in doing so
713 * apart from causing packets to go through here ordered).
715 if (f->ftps_len + f->ftps_seq == ntohl(tcp->th_seq))
716 f->ftps_seq = ntohl(tcp->th_seq);
717 else if (ntohl(tcp->th_seq) + i != f->ftps_seq) {
723 len = MIN(mlen, FTP_BUFSZ / 2);
726 copyout_mblk(m, off, len, wptr);
728 m_copydata(m, off, len, wptr);
734 if (f->ftps_junk == 2)
735 f->ftps_junk = ippr_ftp_valid(rptr, wptr - rptr);
737 while ((f->ftps_junk == 0) && (wptr > rptr)) {
738 f->ftps_junk = ippr_ftp_valid(rptr, wptr - rptr);
739 if (f->ftps_junk == 0) {
743 inc += ippr_ftp_server(fin, ip, nat,
746 inc += ippr_ftp_client(fin, ip, nat,
752 while ((f->ftps_junk == 1) && (rptr < wptr)) {
753 while ((rptr < wptr) && (*rptr != '\r'))
757 if (rptr + 1 < wptr) {
758 if (*(rptr + 1) == '\n') {
770 rptr = wptr = f->ftps_buf;
772 if ((wptr > f->ftps_buf + FTP_BUFSZ / 2)) {
774 if ((rptr == f->ftps_buf) ||
775 (wptr - rptr > FTP_BUFSZ / 2)) {
777 rptr = wptr = f->ftps_buf;
779 bcopy(rptr, f->ftps_buf, i);
780 wptr = f->ftps_buf + i;
789 t->ftps_seq = ntohl(tcp->th_ack);
796 int ippr_ftp_out(fin, ip, aps, nat)
807 return ippr_ftp_process(fin, ip, nat, ftp, 0);
811 int ippr_ftp_in(fin, ip, aps, nat)
822 return ippr_ftp_process(fin, ip, nat, ftp, 1);
827 * ippr_ftp_atoi - implement a version of atoi which processes numbers in
828 * pairs separated by commas (which are expected to be in the range 0 - 255),
829 * returning a 16 bit number combining either side of the , as the MSB and
832 u_short ippr_ftp_atoi(ptr)
835 register char *s = *ptr, c;
836 register u_char i = 0, j = 0;
838 while ((c = *s++) && isdigit(c)) {
846 while ((c = *s++) && isdigit(c)) {