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((int, char *, size_t));
53 int ippr_ftp_server_valid __P((char *, size_t));
54 int ippr_ftp_client_valid __P((char *, size_t));
55 u_short ippr_ftp_atoi __P((char **));
57 static frentry_t ftppxyfr;
58 int ippr_ftp_pasvonly = 0;
59 int ippr_ftp_insecure = 0;
63 * Initialize local structures.
67 bzero((char *)&ftppxyfr, sizeof(ftppxyfr));
69 ftppxyfr.fr_flags = FR_INQUE|FR_PASS|FR_QUICK|FR_KEEPSTATE;
74 int ippr_ftp_new(fin, ip, aps, nat)
83 KMALLOC(ftp, ftpinfo_t *);
87 aps->aps_psiz = sizeof(ftpinfo_t);
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;
101 int ippr_ftp_port(fin, ip, nat, f, dlen)
108 tcphdr_t *tcp, tcph, *tcp2 = &tcph;
109 char newbuf[IPF_FTPBUFSZ], *s;
110 u_int a1, a2, a3, a4;
122 tcp = (tcphdr_t *)fin->fin_dp;
124 * Check for client sending out PORT message.
126 if (dlen < IPF_MINPORTLEN)
128 off = fin->fin_hlen + (tcp->th_off << 2);
130 * Skip the PORT command + space
132 s = f->ftps_rptr + 5;
134 * Pick out the address components, two at a time.
136 a1 = ippr_ftp_atoi(&s);
139 a2 = ippr_ftp_atoi(&s);
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.
148 if (a1 != ntohl(nat->nat_inip.s_addr))
151 a5 = ippr_ftp_atoi(&s);
158 * check for CR-LF at the end.
162 if ((*s == '\r') && (*(s + 1) == '\n')) {
170 * Calculate new address parts for PORT command
172 a1 = ntohl(ip->ip_src.s_addr);
173 a2 = (a1 >> 16) & 0xff;
174 a3 = (a1 >> 8) & 0xff;
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);
182 nlen = strlen(newbuf);
184 if ((inc + ip->ip_len) > 65535)
187 #if !defined(_KERNEL)
188 m = *((mb_t **)fin->fin_mp);
189 bcopy(newbuf, (char *)m + off, nlen);
193 for (m1 = m; m1->b_cont; m1 = m1->b_cont)
195 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
198 /* alloc enough to keep same trailer space for lower driver */
199 nm = allocb(nlen, BPRI_MED);
200 PANIC((!nm),("ippr_ftp_out: allocb failed"));
202 nm->b_band = m1->b_band;
206 PANIC((m1->b_wptr < m1->b_rptr),
207 ("ippr_ftp_out: cannot handle fragmented data block"));
211 if (m1->b_datap->db_struiolim == m1->b_wptr)
212 m1->b_datap->db_struiolim += inc;
213 m1->b_datap->db_struioflag &= ~STRUIO_IP;
216 copyin_mblk(m, off, nlen, newbuf);
218 m = *((mb_t **)fin->fin_mp);
221 /* the mbuf chain will be extended if necessary by m_copyback() */
222 m_copyback(m, off, nlen, newbuf);
224 if (!(m->m_flags & M_PKTHDR))
225 m->m_pkthdr.len += inc;
230 #if (SOLARIS || defined(__sgi)) && defined(_KERNEL)
231 register u_32_t sum1, sum2;
234 sum2 = ip->ip_len + inc;
236 /* Because ~1 == -2, We really need ~1 == -1 */
240 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
242 fix_outcksum(fin, &ip->ip_sum, sum2);
248 * Add skeleton NAT entry for connection which will come back the
253 * Don't allow the PORT command to specify a port < 1024 due to
259 * The server may not make the connection back from port 20, but
260 * it is the most likely so use it here to check for a conflicting
263 bcopy((char *)fin, (char *)&fi, sizeof(fi));
265 fi.fin_data[1] = fin->fin_data[1] - 1;
266 ipn = nat_outlookup(&fi, IPN_TCP, nat->nat_p, nat->nat_inip,
272 ip->ip_len = fin->fin_hlen + sizeof(*tcp2);
273 bzero((char *)tcp2, sizeof(*tcp2));
274 tcp2->th_win = htons(8192);
275 tcp2->th_sport = htons(sp);
277 tcp2->th_flags = TH_SYN;
278 tcp2->th_dport = 0; /* XXX - don't specify remote port */
280 fi.fin_dlen = sizeof(*tcp2);
281 fi.fin_dp = (char *)tcp2;
282 fi.fin_fr = &ftppxyfr;
285 fi.fin_fi.fi_saddr = nat->nat_inip.s_addr;
286 ip->ip_src = nat->nat_inip;
287 ipn = nat_new(&fi, ip, nat->nat_ptr, NULL, IPN_TCP|FI_W_DPORT,
290 ipn->nat_age = fr_defnatage;
291 (void) fr_addstate(ip, &fi, NULL,
292 FI_W_DPORT|FI_IGNOREPKT);
301 int ippr_ftp_client(fin, ip, nat, ftp, dlen)
308 char *rptr, *wptr, cmd[6], c;
313 f = &ftp->ftp_side[0];
317 for (i = 0; (i < 5) && (i < dlen); i++) {
328 if (!strncmp(cmd, "USER ", 5) || !strncmp(cmd, "XAUT ", 5)) {
329 if (ftp->ftp_passok == FTPXY_ADOK_1 ||
330 ftp->ftp_passok == FTPXY_AUOK_1) {
331 ftp->ftp_passok = FTPXY_USER_2;
334 ftp->ftp_passok = FTPXY_USER_1;
337 } else if (!strncmp(cmd, "AUTH ", 5)) {
338 ftp->ftp_passok = FTPXY_AUTH_1;
340 } else if (!strncmp(cmd, "PASS ", 5)) {
341 if (ftp->ftp_passok == FTPXY_USOK_1) {
342 ftp->ftp_passok = FTPXY_PASS_1;
344 } else if (ftp->ftp_passok == FTPXY_USOK_2) {
345 ftp->ftp_passok = FTPXY_PASS_2;
348 } else if ((ftp->ftp_passok == FTPXY_AUOK_1) &&
349 !strncmp(cmd, "ADAT ", 5)) {
350 ftp->ftp_passok = FTPXY_ADAT_1;
352 } else if ((ftp->ftp_passok == FTPXY_PAOK_1 ||
353 ftp->ftp_passok == FTPXY_PAOK_2) &&
354 !strncmp(cmd, "ACCT ", 5)) {
355 ftp->ftp_passok = FTPXY_ACCT_1;
357 } else if ((ftp->ftp_passok == FTPXY_GO) && !ippr_ftp_pasvonly &&
358 !strncmp(cmd, "PORT ", 5)) {
359 inc = ippr_ftp_port(fin, ip, nat, f, dlen);
360 } else if (ippr_ftp_insecure && !ippr_ftp_pasvonly &&
361 !strncmp(cmd, "PORT ", 5)) {
362 inc = ippr_ftp_port(fin, ip, nat, f, dlen);
365 while ((*rptr++ != '\n') && (rptr < wptr))
372 int ippr_ftp_pasv(fin, ip, nat, f, dlen)
379 tcphdr_t *tcp, tcph, *tcp2 = &tcph;
380 struct in_addr swip, swip2;
381 u_int a1, a2, a3, a4;
388 #define PASV_REPLEN 24
390 * Check for PASV reply message.
392 if (dlen < IPF_MIN227LEN)
394 else if (strncmp(f->ftps_rptr, "227 Entering Passive Mod", PASV_REPLEN))
397 tcp = (tcphdr_t *)fin->fin_dp;
400 * Skip the PORT command + space
402 s = f->ftps_rptr + PASV_REPLEN;
403 while (*s && !isdigit(*s))
406 * Pick out the address components, two at a time.
408 a1 = ippr_ftp_atoi(&s);
411 a2 = ippr_ftp_atoi(&s);
416 * check that IP address in the PORT/PASV reply is the same as the
417 * sender of the command - prevents using PORT for port scanning.
421 if (a1 != ntohl(nat->nat_oip.s_addr))
424 a5 = ippr_ftp_atoi(&s);
435 * check for CR-LF at the end.
437 if ((*s == '\r') && (*(s + 1) == '\n')) {
444 * Calculate new address parts for 227 reply
446 a1 = ntohl(ip->ip_src.s_addr);
447 a2 = (a1 >> 16) & 0xff;
448 a3 = (a1 >> 8) & 0xff;
453 olen = s - f->ftps_rptr;
454 (void) sprintf(newbuf, "%s %u,%u,%u,%u,%u,%u\r\n",
455 "227 Entering Passive Mode", a1, a2, a3, a4, a5, a6);
456 nlen = strlen(newbuf);
458 if ((inc + ip->ip_len) > 65535)
461 #if !defined(_KERNEL)
462 m = *((mb_t **)fin->fin_mp);
463 m_copyback(m, off, nlen, newbuf);
467 for (m1 = m; m1->b_cont; m1 = m1->b_cont)
469 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
472 /* alloc enough to keep same trailer space for lower driver */
473 nm = allocb(nlen, BPRI_MED);
474 PANIC((!nm),("ippr_ftp_out: allocb failed"));
476 nm->b_band = m1->b_band;
480 PANIC((m1->b_wptr < m1->b_rptr),
481 ("ippr_ftp_out: cannot handle fragmented data block"));
487 /*copyin_mblk(m, off, nlen, newbuf);*/
489 m = *((mb_t **)fin->fin_mp);
492 /* the mbuf chain will be extended if necessary by m_copyback() */
493 /*m_copyback(m, off, nlen, newbuf);*/
494 # endif /* SOLARIS */
497 #if (SOLARIS || defined(__sgi)) && defined(_KERNEL)
498 register u_32_t sum1, sum2;
501 sum2 = ip->ip_len + inc;
503 /* Because ~1 == -2, We really need ~1 == -1 */
507 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
509 fix_outcksum(fin, &ip->ip_sum, sum2);
510 #endif /* SOLARIS || defined(__sgi) */
516 * Add skeleton NAT entry for connection which will come back the
519 bcopy((char *)fin, (char *)&fi, sizeof(fi));
521 dp = htons(fin->fin_data[1] - 1);
522 fi.fin_data[1] = ntohs(dp);
523 ipn = nat_outlookup(&fi, IPN_TCP, nat->nat_p, nat->nat_inip,
529 ip->ip_len = fin->fin_hlen + sizeof(*tcp2);
530 bzero((char *)tcp2, sizeof(*tcp2));
531 tcp2->th_win = htons(8192);
532 tcp2->th_sport = 0; /* XXX - fake it for nat_new */
534 tcp2->th_flags = TH_SYN;
535 fi.fin_data[1] = a5 << 8 | a6;
536 fi.fin_dlen = sizeof(*tcp2);
537 tcp2->th_dport = htons(fi.fin_data[1]);
539 fi.fin_dp = (char *)tcp2;
540 fi.fin_fr = &ftppxyfr;
544 fi.fin_fi.fi_daddr = ip->ip_src.s_addr;
545 fi.fin_fi.fi_saddr = nat->nat_inip.s_addr;
546 ip->ip_dst = ip->ip_src;
547 ip->ip_src = nat->nat_inip;
548 ipn = nat_new(&fi, ip, nat->nat_ptr, NULL, IPN_TCP|FI_W_SPORT,
551 ipn->nat_age = fr_defnatage;
552 (void) fr_addstate(ip, &fi, NULL,
553 FI_W_SPORT|FI_IGNOREPKT);
563 int ippr_ftp_server(fin, ip, nat, ftp, dlen)
575 f = &ftp->ftp_side[1];
579 if (!isdigit(*rptr) || !isdigit(*(rptr + 1)) || !isdigit(*(rptr + 2)))
581 if (ftp->ftp_passok == FTPXY_GO) {
582 if (!strncmp(rptr, "227 ", 4))
583 inc = ippr_ftp_pasv(fin, ip, nat, f, dlen);
584 } else if (ippr_ftp_insecure && !strncmp(rptr, "227 ", 4)) {
585 inc = ippr_ftp_pasv(fin, ip, nat, f, dlen);
586 } else if (*rptr == '5' || *rptr == '4')
587 ftp->ftp_passok = FTPXY_INIT;
588 else if (ftp->ftp_incok) {
590 if (ftp->ftp_passok == FTPXY_ACCT_1)
591 ftp->ftp_passok = FTPXY_GO;
594 } else if (*rptr == '2') {
595 switch (ftp->ftp_passok)
602 ftp->ftp_passok = FTPXY_GO;
605 ftp->ftp_passok += 3;
611 while ((*rptr++ != '\n') && (rptr < wptr))
619 * Look to see if the buffer starts with something which we recognise as
620 * being the correct syntax for the FTP protocol.
622 int ippr_ftp_client_valid(buf, len)
627 register size_t i = len;
647 if ((c != ' ') && (c != '\r'))
649 } else if ((c != ' ') && (c != '\r'))
666 int ippr_ftp_server_valid(buf, len)
671 register size_t i = len;
688 if ((c != '-') && (c != ' '))
705 int ippr_ftp_valid(side, buf, len)
713 ret = ippr_ftp_client_valid(buf, len);
715 ret = ippr_ftp_server_valid(buf, len);
720 int ippr_ftp_process(fin, ip, nat, ftp, rv)
727 int mlen, len, off, inc, i, sel;
733 tcp = (tcphdr_t *)fin->fin_dp;
734 off = fin->fin_hlen + (tcp->th_off << 2);
736 #if SOLARIS && defined(_KERNEL)
739 m = *((mb_t **)fin->fin_mp);
748 mlen = mbufchainlen(m);
753 t = &ftp->ftp_side[1 - rv];
754 f = &ftp->ftp_side[rv];
757 (int)ntohl(tcp->th_ack) - (int)t->ftps_seq > 0)
758 t->ftps_seq = ntohl(tcp->th_ack);
767 sel = nat->nat_aps->aps_sel[1 - rv];
769 if (nat->nat_aps->aps_ackmin[sel] < ntohl(tcp->th_seq))
770 i = nat->nat_aps->aps_ackoff[sel];
772 if (nat->nat_aps->aps_seqmin[sel] < ntohl(tcp->th_seq))
773 i = nat->nat_aps->aps_seqoff[sel];
776 * XXX - Ideally, this packet should get dropped because we now know
777 * that it is out of order (and there is no real danger in doing so
778 * apart from causing packets to go through here ordered).
780 if (f->ftps_len + f->ftps_seq == ntohl(tcp->th_seq))
781 f->ftps_seq = ntohl(tcp->th_seq);
783 inc = ntohl(tcp->th_seq) - f->ftps_seq;
796 len = MIN(mlen, FTP_BUFSZ / 2);
798 #if !defined(_KERNEL)
799 bcopy((char *)m + off, wptr, len);
802 copyout_mblk(m, off, len, wptr);
804 m_copydata(m, off, len, wptr);
811 if (f->ftps_junk == 2)
812 f->ftps_junk = ippr_ftp_valid(rv, rptr, wptr - rptr);
814 while ((f->ftps_junk == 0) && (wptr > rptr)) {
815 f->ftps_junk = ippr_ftp_valid(rv, rptr, wptr - rptr);
816 if (f->ftps_junk == 0) {
821 inc += ippr_ftp_server(fin, ip, nat,
824 inc += ippr_ftp_client(fin, ip, nat,
832 * Off to a bad start so lets just forget about using the
833 * ftp proxy for this connection.
835 if ((f->ftps_cmds == 0) && (f->ftps_junk == 1)) {
839 while ((f->ftps_junk == 1) && (rptr < wptr)) {
840 while ((rptr < wptr) && (*rptr != '\r'))
844 if (rptr + 1 < wptr) {
845 if (*(rptr + 1) == '\n') {
857 rptr = wptr = f->ftps_buf;
859 if ((wptr > f->ftps_buf + FTP_BUFSZ / 2)) {
861 if ((rptr == f->ftps_buf) ||
862 (wptr - rptr > FTP_BUFSZ / 2)) {
864 rptr = wptr = f->ftps_buf;
866 bcopy(rptr, f->ftps_buf, i);
867 wptr = f->ftps_buf + i;
876 t->ftps_seq = ntohl(tcp->th_ack);
883 int ippr_ftp_out(fin, ip, aps, nat)
894 return ippr_ftp_process(fin, ip, nat, ftp, 0);
898 int ippr_ftp_in(fin, ip, aps, nat)
909 return ippr_ftp_process(fin, ip, nat, ftp, 1);
914 * ippr_ftp_atoi - implement a version of atoi which processes numbers in
915 * pairs separated by commas (which are expected to be in the range 0 - 255),
916 * returning a 16 bit number combining either side of the , as the MSB and
919 u_short ippr_ftp_atoi(ptr)
922 register char *s = *ptr, c;
923 register u_char i = 0, j = 0;
925 while ((c = *s++) && isdigit(c)) {
933 while ((c = *s++) && isdigit(c)) {