/* FTP extension for TCP NAT alteration. */ #include #include #include #include #include #include #include #include #include #include #if 0 #define DEBUGP printk #else #define DEBUGP(format, args...) #endif #define MAX_PORTS 8 static int ports[MAX_PORTS]; static int ports_c = 0; #ifdef MODULE_PARM MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i"); #endif DECLARE_LOCK_EXTERN(ip_ftp_lock); /* FIXME: Time out? --RR */ static int ftp_nat_expected(struct sk_buff **pskb, unsigned int hooknum, struct ip_conntrack *ct, struct ip_nat_info *info, struct ip_conntrack *master, struct ip_nat_info *masterinfo, unsigned int *verdict) { struct ip_nat_multi_range mr; u_int32_t newdstip, newsrcip, newip; struct ip_ct_ftp *ftpinfo; IP_NF_ASSERT(info); IP_NF_ASSERT(master); IP_NF_ASSERT(masterinfo); IP_NF_ASSERT(!(info->initialized & (1<help.ct_ftp_info; LOCK_BH(&ip_ftp_lock); if (ftpinfo->is_ftp != 21) { UNLOCK_BH(&ip_ftp_lock); DEBUGP("nat_expected: master not ftp\n"); return 0; } if (ftpinfo->ftptype == IP_CT_FTP_PORT || ftpinfo->ftptype == IP_CT_FTP_EPRT) { /* PORT command: make connection go to the client. */ newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; newsrcip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip; DEBUGP("nat_expected: PORT cmd. %u.%u.%u.%u->%u.%u.%u.%u\n", NIPQUAD(newsrcip), NIPQUAD(newdstip)); } else { /* PASV command: make the connection go to the server */ newdstip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip; newsrcip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; DEBUGP("nat_expected: PASV cmd. %u.%u.%u.%u->%u.%u.%u.%u\n", NIPQUAD(newsrcip), NIPQUAD(newdstip)); } UNLOCK_BH(&ip_ftp_lock); if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) newip = newsrcip; else newip = newdstip; DEBUGP("nat_expected: IP to %u.%u.%u.%u\n", NIPQUAD(newip)); mr.rangesize = 1; /* We don't want to manip the per-protocol, just the IPs... */ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS; mr.range[0].min_ip = mr.range[0].max_ip = newip; /* ... unless we're doing a MANIP_DST, in which case, make sure we map to the correct port */ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) { mr.range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED; mr.range[0].min = mr.range[0].max = ((union ip_conntrack_manip_proto) { htons(ftpinfo->port) }); } *verdict = ip_nat_setup_info(ct, &mr, hooknum); return 1; } static int mangle_rfc959_packet(struct sk_buff **pskb, u_int32_t newip, u_int16_t port, unsigned int matchoff, unsigned int matchlen, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { char buffer[sizeof("nnn,nnn,nnn,nnn,nnn,nnn")]; MUST_BE_LOCKED(&ip_ftp_lock); sprintf(buffer, "%u,%u,%u,%u,%u,%u", NIPQUAD(newip), port>>8, port&0xFF); DEBUGP("calling ip_nat_mangle_tcp_packet\n"); return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff, matchlen, buffer, strlen(buffer)); } /* |1|132.235.1.2|6275| */ static int mangle_eprt_packet(struct sk_buff **pskb, u_int32_t newip, u_int16_t port, unsigned int matchoff, unsigned int matchlen, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { char buffer[sizeof("|1|255.255.255.255|65535|")]; MUST_BE_LOCKED(&ip_ftp_lock); sprintf(buffer, "|1|%u.%u.%u.%u|%u|", NIPQUAD(newip), port); DEBUGP("calling ip_nat_mangle_tcp_packet\n"); return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff, matchlen, buffer, strlen(buffer)); } /* |1|132.235.1.2|6275| */ static int mangle_epsv_packet(struct sk_buff **pskb, u_int32_t newip, u_int16_t port, unsigned int matchoff, unsigned int matchlen, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { char buffer[sizeof("|||65535|")]; MUST_BE_LOCKED(&ip_ftp_lock); sprintf(buffer, "|||%u|", port); DEBUGP("calling ip_nat_mangle_tcp_packet\n"); return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff, matchlen, buffer, strlen(buffer)); } static int (*mangle[])(struct sk_buff **, u_int32_t, u_int16_t, unsigned int, unsigned int, struct ip_conntrack *, enum ip_conntrack_info) = { [IP_CT_FTP_PORT] mangle_rfc959_packet, [IP_CT_FTP_PASV] mangle_rfc959_packet, [IP_CT_FTP_EPRT] mangle_eprt_packet, [IP_CT_FTP_EPSV] mangle_epsv_packet }; static int ftp_data_fixup(const struct ip_ct_ftp *ct_ftp_info, struct ip_conntrack *ct, unsigned int datalen, struct sk_buff **pskb, enum ip_conntrack_info ctinfo) { u_int32_t newip; struct iphdr *iph = (*pskb)->nh.iph; struct tcphdr *tcph = (void *)iph + iph->ihl*4; u_int16_t port; struct ip_conntrack_tuple tuple; /* Don't care about source port */ const struct ip_conntrack_tuple mask = { { 0xFFFFFFFF, { 0 } }, { 0xFFFFFFFF, { 0xFFFF }, 0xFFFF } }; memset(&tuple, 0, sizeof(tuple)); MUST_BE_LOCKED(&ip_ftp_lock); DEBUGP("FTP_NAT: seq %u + %u in %u + %u\n", ct_ftp_info->seq, ct_ftp_info->len, ntohl(tcph->seq), datalen); /* Change address inside packet to match way we're mapping this connection. */ if (ct_ftp_info->ftptype == IP_CT_FTP_PASV || ct_ftp_info->ftptype == IP_CT_FTP_EPSV) { /* PASV/EPSV response: must be where client thinks server is */ newip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip; /* Expect something from client->server */ tuple.src.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip; tuple.dst.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip; } else { /* PORT command: must be where server thinks client is */ newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; /* Expect something from server->client */ tuple.src.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip; tuple.dst.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip; } tuple.dst.protonum = IPPROTO_TCP; /* Try to get same port: if not, try to change it. */ for (port = ct_ftp_info->port; port != 0; port++) { tuple.dst.u.tcp.port = htons(port); if (ip_conntrack_expect_related(ct, &tuple, &mask, NULL) == 0) break; } if (port == 0) return 0; if (!mangle[ct_ftp_info->ftptype](pskb, newip, port, ct_ftp_info->seq - ntohl(tcph->seq), ct_ftp_info->len, ct, ctinfo)) return 0; return 1; } static unsigned int help(struct ip_conntrack *ct, struct ip_nat_info *info, enum ip_conntrack_info ctinfo, unsigned int hooknum, struct sk_buff **pskb) { struct iphdr *iph = (*pskb)->nh.iph; struct tcphdr *tcph = (void *)iph + iph->ihl*4; unsigned int datalen; int dir; int score; struct ip_ct_ftp *ct_ftp_info = &ct->help.ct_ftp_info; /* Delete SACK_OK on initial TCP SYNs. */ if (tcph->syn && !tcph->ack) ip_nat_delete_sack(*pskb, tcph); /* Only mangle things once: original direction in POST_ROUTING and reply direction on PRE_ROUTING. */ dir = CTINFO2DIR(ctinfo); if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL) || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY))) { DEBUGP("nat_ftp: Not touching dir %s at hook %s\n", dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY", hooknum == NF_IP_POST_ROUTING ? "POSTROUTING" : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING" : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???"); return NF_ACCEPT; } datalen = (*pskb)->len - iph->ihl * 4 - tcph->doff * 4; score = 0; LOCK_BH(&ip_ftp_lock); if (ct_ftp_info->len) { /* If it's in the right range... */ score += between(ct_ftp_info->seq, ntohl(tcph->seq), ntohl(tcph->seq) + datalen); score += between(ct_ftp_info->seq + ct_ftp_info->len, ntohl(tcph->seq), ntohl(tcph->seq) + datalen); if (score == 1) { /* Half a match? This means a partial retransmisison. It's a cracker being funky. */ if (net_ratelimit()) { printk("FTP_NAT: partial packet %u/%u in %u/%u\n", ct_ftp_info->seq, ct_ftp_info->len, ntohl(tcph->seq), ntohl(tcph->seq) + datalen); } UNLOCK_BH(&ip_ftp_lock); return NF_DROP; } else if (score == 2) { if (!ftp_data_fixup(ct_ftp_info, ct, datalen, pskb, ctinfo)) { UNLOCK_BH(&ip_ftp_lock); return NF_DROP; } /* skb may have been reallocated */ iph = (*pskb)->nh.iph; tcph = (void *)iph + iph->ihl*4; } } UNLOCK_BH(&ip_ftp_lock); ip_nat_seq_adjust(*pskb, ct, ctinfo); return NF_ACCEPT; } static struct ip_nat_helper ftp[MAX_PORTS]; static char ftp_names[MAX_PORTS][6]; static struct ip_nat_expect ftp_expect = { { NULL, NULL }, ftp_nat_expected }; /* Not __exit: called from init() */ static void fini(void) { int i; for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { DEBUGP("ip_nat_ftp: unregistering port %d\n", ports[i]); ip_nat_helper_unregister(&ftp[i]); } ip_nat_expect_unregister(&ftp_expect); } static int __init init(void) { int i, ret; char *tmpname; ret = ip_nat_expect_register(&ftp_expect); if (ret == 0) { if (ports[0] == 0) ports[0] = 21; for (i = 0; (i < MAX_PORTS) && ports[i]; i++) { memset(&ftp[i], 0, sizeof(struct ip_nat_helper)); ftp[i].tuple.dst.protonum = IPPROTO_TCP; ftp[i].tuple.src.u.tcp.port = htons(ports[i]); ftp[i].mask.dst.protonum = 0xFFFF; ftp[i].mask.src.u.tcp.port = 0xFFFF; ftp[i].help = help; tmpname = &ftp_names[i][0]; sprintf(tmpname, "ftp%2.2d", i); ftp[i].name = tmpname; DEBUGP("ip_nat_ftp: Trying to register for port %d\n", ports[i]); ret = ip_nat_helper_register(&ftp[i]); if (ret) { printk("ip_nat_ftp: error registering helper for port %d\n", ports[i]); fini(); return ret; } ports_c++; } } else { ip_nat_expect_unregister(&ftp_expect); } return ret; } module_init(init); module_exit(fini); MODULE_LICENSE("GPL");