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)
189 for (m1 = m; m1->b_cont; m1 = m1->b_cont)
191 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
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"));
198 nm->b_band = m1->b_band;
202 PANIC((m1->b_wptr < m1->b_rptr),
203 ("ippr_ftp_out: cannot handle fragmented data block"));
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;
212 copyin_mblk(m, off, nlen, newbuf);
214 m = *((mb_t **)fin->fin_mp);
217 /* the mbuf chain will be extended if necessary by m_copyback() */
218 m_copyback(m, off, nlen, newbuf);
220 if (!(m->m_flags & M_PKTHDR))
221 m->m_pkthdr.len += inc;
225 #if SOLARIS || defined(__sgi)
226 register u_32_t sum1, sum2;
229 sum2 = ip->ip_len + inc;
231 /* Because ~1 == -2, We really need ~1 == -1 */
235 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
237 fix_outcksum(fin, &ip->ip_sum, sum2);
243 * Add skeleton NAT entry for connection which will come back the
248 * Don't allow the PORT command to specify a port < 1024 due to
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
258 bcopy((char *)fin, (char *)&fi, sizeof(fi));
260 fi.fin_data[1] = fin->fin_data[1] - 1;
261 ipn = nat_outlookup(&fi, IPN_TCP, nat->nat_p, nat->nat_inip,
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);
272 tcp2->th_dport = 0; /* XXX - don't specify remote port */
274 fi.fin_dlen = sizeof(*tcp2);
275 fi.fin_dp = (char *)tcp2;
276 fi.fin_fr = &ftppxyfr;
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,
284 ipn->nat_age = fr_defnatage;
285 (void) fr_addstate(ip, &fi, NULL,
286 FI_W_DPORT|FI_IGNOREPKT);
295 int ippr_ftp_client(fin, ip, nat, ftp, dlen)
302 char *rptr, *wptr, cmd[6], c;
307 f = &ftp->ftp_side[0];
311 for (i = 0; (i < 5) && (i < dlen); i++) {
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;
328 ftp->ftp_passok = FTPXY_USER_1;
331 } else if (!strncmp(cmd, "AUTH ", 5)) {
332 ftp->ftp_passok = FTPXY_AUTH_1;
334 } else if (!strncmp(cmd, "PASS ", 5)) {
335 if (ftp->ftp_passok == FTPXY_USOK_1) {
336 ftp->ftp_passok = FTPXY_PASS_1;
338 } else if (ftp->ftp_passok == FTPXY_USOK_2) {
339 ftp->ftp_passok = FTPXY_PASS_2;
342 } else if ((ftp->ftp_passok == FTPXY_AUOK_1) &&
343 !strncmp(cmd, "ADAT ", 5)) {
344 ftp->ftp_passok = FTPXY_ADAT_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;
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);
359 while ((*rptr++ != '\n') && (rptr < wptr))
366 int ippr_ftp_pasv(fin, ip, nat, f, dlen)
373 tcphdr_t *tcp, tcph, *tcp2 = &tcph;
374 struct in_addr swip, swip2;
375 u_int a1, a2, a3, a4;
382 #define PASV_REPLEN 24
384 * Check for PASV reply message.
386 if (dlen < IPF_MIN227LEN)
388 else if (strncmp(f->ftps_rptr, "227 Entering Passive Mod", PASV_REPLEN))
391 tcp = (tcphdr_t *)fin->fin_dp;
394 * Skip the PORT command + space
396 s = f->ftps_rptr + PASV_REPLEN;
397 while (*s && !isdigit(*s))
400 * Pick out the address components, two at a time.
402 a1 = ippr_ftp_atoi(&s);
405 a2 = ippr_ftp_atoi(&s);
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.
415 if (a1 != ntohl(nat->nat_oip.s_addr))
418 a5 = ippr_ftp_atoi(&s);
429 * check for CR-LF at the end.
431 if ((*s == '\r') && (*(s + 1) == '\n')) {
438 * Calculate new address parts for 227 reply
440 a1 = ntohl(ip->ip_src.s_addr);
441 a2 = (a1 >> 16) & 0xff;
442 a3 = (a1 >> 8) & 0xff;
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);
452 if ((inc + ip->ip_len) > 65535)
457 for (m1 = m; m1->b_cont; m1 = m1->b_cont)
459 if ((inc > 0) && (m1->b_datap->db_lim - m1->b_wptr < inc)) {
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"));
466 nm->b_band = m1->b_band;
470 PANIC((m1->b_wptr < m1->b_rptr),
471 ("ippr_ftp_out: cannot handle fragmented data block"));
477 /*copyin_mblk(m, off, nlen, newbuf);*/
479 m = *((mb_t **)fin->fin_mp);
482 /* the mbuf chain will be extended if necessary by m_copyback() */
483 /*m_copyback(m, off, nlen, newbuf);*/
486 #if SOLARIS || defined(__sgi)
487 register u_32_t sum1, sum2;
490 sum2 = ip->ip_len + inc;
492 /* Because ~1 == -2, We really need ~1 == -1 */
496 sum2 = (sum2 & 0xffff) + (sum2 >> 16);
498 fix_outcksum(fin, &ip->ip_sum, sum2);
499 #endif /* SOLARIS || defined(__sgi) */
505 * Add skeleton NAT entry for connection which will come back the
508 bcopy((char *)fin, (char *)&fi, sizeof(fi));
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,
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 */
523 fi.fin_data[1] = a5 << 8 | a6;
524 fi.fin_dlen = sizeof(*tcp2);
525 tcp2->th_dport = htons(fi.fin_data[1]);
527 fi.fin_dp = (char *)tcp2;
528 fi.fin_fr = &ftppxyfr;
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,
539 ipn->nat_age = fr_defnatage;
540 (void) fr_addstate(ip, &fi, NULL,
541 FI_W_SPORT|FI_IGNOREPKT);
551 int ippr_ftp_server(fin, ip, nat, ftp, dlen)
563 f = &ftp->ftp_side[1];
567 if (!isdigit(*rptr) || !isdigit(*(rptr + 1)) || !isdigit(*(rptr + 2)))
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) {
578 if (ftp->ftp_passok == FTPXY_ACCT_1)
579 ftp->ftp_passok = FTPXY_GO;
582 } else if (*rptr == '2') {
583 switch (ftp->ftp_passok)
590 ftp->ftp_passok = FTPXY_GO;
593 ftp->ftp_passok += 3;
599 while ((*rptr++ != '\n') && (rptr < wptr))
607 * Look to see if the buffer starts with something which we recognise as
608 * being the correct syntax for the FTP protocol.
610 int ippr_ftp_client_valid(buf, len)
615 register size_t i = len;
635 if ((c != ' ') && (c != '\r'))
637 } else if ((c != ' ') && (c != '\r'))
654 int ippr_ftp_server_valid(buf, len)
659 register size_t i = len;
676 if ((c != '-') && (c != ' '))
693 int ippr_ftp_valid(side, buf, len)
701 ret = ippr_ftp_client_valid(buf, len);
703 ret = ippr_ftp_server_valid(buf, len);
708 int ippr_ftp_process(fin, ip, nat, ftp, rv)
715 int mlen, len, off, inc, i, sel;
721 tcp = (tcphdr_t *)fin->fin_dp;
722 off = fin->fin_hlen + (tcp->th_off << 2);
727 m = *((mb_t **)fin->fin_mp);
731 mlen = msgdsize(m) - off;
733 mlen = mbufchainlen(m) - off;
736 t = &ftp->ftp_side[1 - rv];
737 f = &ftp->ftp_side[rv];
740 (int)ntohl(tcp->th_ack) - (int)t->ftps_seq > 0)
741 t->ftps_seq = ntohl(tcp->th_ack);
750 sel = nat->nat_aps->aps_sel[1 - rv];
752 i = nat->nat_aps->aps_ackoff[sel];
754 i = nat->nat_aps->aps_seqoff[sel];
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).
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) {
768 len = MIN(mlen, FTP_BUFSZ / 2);
771 copyout_mblk(m, off, len, wptr);
773 m_copydata(m, off, len, wptr);
779 if (f->ftps_junk == 2)
780 f->ftps_junk = ippr_ftp_valid(rv, rptr, wptr - rptr);
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) {
789 inc += ippr_ftp_server(fin, ip, nat,
792 inc += ippr_ftp_client(fin, ip, nat,
800 * Off to a bad start so lets just forget about using the
801 * ftp proxy for this connection.
803 if ((f->ftps_cmds == 0) && (f->ftps_junk == 1))
806 while ((f->ftps_junk == 1) && (rptr < wptr)) {
807 while ((rptr < wptr) && (*rptr != '\r'))
811 if (rptr + 1 < wptr) {
812 if (*(rptr + 1) == '\n') {
824 rptr = wptr = f->ftps_buf;
826 if ((wptr > f->ftps_buf + FTP_BUFSZ / 2)) {
828 if ((rptr == f->ftps_buf) ||
829 (wptr - rptr > FTP_BUFSZ / 2)) {
831 rptr = wptr = f->ftps_buf;
833 bcopy(rptr, f->ftps_buf, i);
834 wptr = f->ftps_buf + i;
843 t->ftps_seq = ntohl(tcp->th_ack);
850 int ippr_ftp_out(fin, ip, aps, nat)
861 return ippr_ftp_process(fin, ip, nat, ftp, 0);
865 int ippr_ftp_in(fin, ip, aps, nat)
876 return ippr_ftp_process(fin, ip, nat, ftp, 1);
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
886 u_short ippr_ftp_atoi(ptr)
889 register char *s = *ptr, c;
890 register u_char i = 0, j = 0;
892 while ((c = *s++) && isdigit(c)) {
900 while ((c = *s++) && isdigit(c)) {