--- linux-2.2.21/net/ipv4/ip_masq_ftp.c.orig Tue Mar 27 09:33:49 2001 +++ linux-2.2.21/net/ipv4/ip_masq_ftp.c Sun Sep 22 22:57:46 2002 @@ -5,28 +5,30 @@ * Version: @(#)ip_masq_ftp.c 0.10 20/09/00 * * Author: Wouter Gadeyne - * + * * * Fixes: * Wouter Gadeyne : Fixed masquerading support of ftp PORT commands - * Juan Jose Ciarlante : Code moved and adapted from ip_fw.c - * Keith Owens : Add keep alive for ftp control channel + * Juan Jose Ciarlante : Code moved and adapted from ip_fw.c + * Keith Owens : Add keep alive for ftp control channel * Nigel Metheringham : Added multiple port support - * Juan Jose Ciarlante : Use control_add() for ftp control chan - * Juan Jose Ciarlante : Litl bits for 2.1 - * Juan Jose Ciarlante : use ip_masq_listen() - * Juan Jose Ciarlante : use private app_data for own flag(s) + * Juan Jose Ciarlante : Use control_add() for ftp control chan + * Juan Jose Ciarlante : Litl bits for 2.1 + * Juan Jose Ciarlante : use ip_masq_listen() + * Juan Jose Ciarlante : use private app_data for own flag(s) * Bjarni R. Einarsson : Added protection against "extended FTP ALG attack" * Neil Toronto : portfw FTP support * Juan Jose Ciarlante : reimplemented parsing logic, merged portfw FTP support for PASV (new "in_ports" module param), use th->doff for data offset * Juan Jose Ciarlante : safe_mem_eq2() and size adjustments for less CPU * Juan Jose Ciarlante : fwmark hook-able + * Wensong Zhang : Changed the server data port of active mode to the control connection port minus 1 in portfw (or lvs) FTP support. + * Added inbound ftp_pasv checking, and fixed many IP_MASQ_DEBUGs to display address/port in right order. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License. - * + * * Multiple Port Support * The helper can be made to handle up to MAX_MASQ_APP_PORTS (normally 12) * with the port numbers being defined at module load time. The module @@ -38,28 +40,28 @@ * where modload will pick it up should you use modload to load your * modules. * Additional portfw Port Support - * Module parameter "in_ports" specifies the list of forwarded ports - * at firewall (portfw and friends) that must be hooked to allow - * PASV connections to inside servers. - * Same as before: - * in_ports=fw1,fw2,... - * Eg: + * Module parameter "in_ports" specifies the list of forwarded ports + * at firewall (portfw and friends) that must be hooked to allow + * PASV connections to inside servers. + * Same as before: + * in_ports=fw1,fw2,... + * Eg: * ipmasqadm portfw -a -P tcp -L a.b.c.d 2021 -R 192.168.1.1 21 * ipmasqadm portfw -a -P tcp -L a.b.c.d 8021 -R 192.168.1.1 21 * modprobe ip_masq_ftp in_ports=2021,8021 - * + * * Protection against the "extended FTP ALG vulnerability". * This vulnerability was reported in: * * http://www.securityfocus.com/templates/archive.pike?list=82&date=2000-03-08&msg=38C8C8EE.544524B1@xxxxxxxxxxx - * - * The protection here is very simplistic, but it at least denies access - * to all ports under 1024, and allows the user to specify an additional + * + * The protection here is very simplistic, but it at least denies access + * to all ports under 1024, and allows the user to specify an additional * list of high ports on the insmod command line, like this: * noport=x1,x2,x3, ... - * Up to MAX_MASQ_APP_PORTS (normally 12) ports may be specified, the + * Up to MAX_MASQ_APP_PORTS (normally 12) ports may be specified, the * default blocks access to the X server (port 6000) only. - * + * * Patch by Bjarni R. Einarsson . The original patch is * available at: http://bre.klaki.net/programs/ip_masq_ftp.2000-03-20.diff */ @@ -76,15 +78,15 @@ #include #include -/* #define IP_MASQ_NDEBUG */ +/* #define CONFIG_IP_MASQ_DEBUG */ #include -/* - * paranoid, CPU care offset handling +/* + * paranoid, CPU care offset handling */ /* #define MASQ_FTP_RELAXED 1 */ #ifdef MASQ_FTP_RELAXED -#define _N(x) 0 +#define _N(x) 0 #else #define _N(x) (x) #endif @@ -92,11 +94,11 @@ #define IP_MASQ_FTP_RPAREN 0x01 /* stream has ')' char */ /* - * Eat 1 port (last elem) for holding firewall mark instance + * Eat 1 port (last elem) for holding firewall mark instance */ #define MAX_MASQ_FTP_PORTS (MAX_MASQ_APP_PORTS-1) #define MAX_MASQ_FTP_PORTS_MODPARM 11 -/* +/* * List of ports (up to MAX_MASQ_APP_PORTS) to be handled by helper * First port is set to the default port. */ @@ -104,16 +106,16 @@ static struct ip_masq_app *masq_ftp_objs[MAX_MASQ_APP_PORTS]; /* - * in (forwarded) ports + * in (forwarded) ports */ -static int in_ports[MAX_MASQ_FTP_PORTS] = {0}; +static int in_ports[MAX_MASQ_FTP_PORTS] = {0}; static struct ip_masq_app *masq_in_ftp_objs[MAX_MASQ_APP_PORTS]; #define masq_ftp_mark masq_ftp_objs[MAX_MASQ_APP_PORTS-1] #define masq_in_ftp_mark masq_in_ftp_objs[MAX_MASQ_APP_PORTS-1] /* - * List of ports (up to MAX_MASQ_APP_PORTS) we don't allow ftp-data + * List of ports (up to MAX_MASQ_APP_PORTS) we don't allow ftp-data * connections to. Default is to block connections to port 6000 (X servers). * This is in addition to all ports under 1024. */ @@ -140,6 +142,7 @@ /* Dummy variable */ static int masq_ftp_pasv; +static int masq_in_ftp_pasv; /* * This function parses the IP address and Port number found in PORT commands @@ -150,7 +153,7 @@ static __u32 parse_ip_port( char **datap, __u16 *portp ) { char *data = *datap; -#if CONFIG_IP_MASQ_DEBUG +#ifdef CONFIG_IP_MASQ_DEBUG char *data0=data; #endif unsigned char p1,p2,p3,p4,p5,p6; @@ -172,7 +175,7 @@ return 0; p6 = simple_strtoul(data+1, &data, 10); - IP_MASQ_DEBUG(2-debug, "FTP: parse_ip_port() Ok: \"%*s\" size=%d\n", + IP_MASQ_DEBUG(2-debug, "FTP: parse_ip_port() Ok: \"%*s\" size=%d\n", data-data0, data0, data-data0); @@ -185,21 +188,21 @@ static int masq_ftp_init_1 (struct ip_masq_app *mapp, struct ip_masq *ms) { - MOD_INC_USE_COUNT; - return 0; + MOD_INC_USE_COUNT; + return 0; } static int masq_ftp_done_1 (struct ip_masq_app *mapp, struct ip_masq *ms) { - MOD_DEC_USE_COUNT; - return 0; + MOD_DEC_USE_COUNT; + return 0; } static int masq_ftp_unsafe(__u32 from_ip, __u16 from_port) { int i; - if (from_port < 1024) + if (from_port < 1024) { IP_MASQ_DEBUG(1-debug, "Unsafe PORT %d.%d.%d.%d:%d detected, ignored\n",NIPQUAD(from_ip),from_port); return 1; @@ -217,11 +220,11 @@ * carefully compare with any of these 2 strings, eating stream pointer * as it proceeds * Returns: - * NULL not matched - * !NULL last matched char* + * NULL not matched + * !NULL last matched char* */ static char* safe_mem_eq2(char *data, const char *data_limit, int size, const char *str1, const char *str2) { -#if CONFIG_IP_MASQ_DEBUG +#ifdef CONFIG_IP_MASQ_DEBUG const char *data0=data; if (!data) { IP_MASQ_ERR("FTP: NULL data passed to safe_mem_eq2()!!!"); @@ -234,9 +237,9 @@ data_limit -= size; while (data <= data_limit) { - if (memcmp(data,str1,size)==0) + if (memcmp(data,str1,size)==0) goto equal_ok; - if (str2 && memcmp(data,str2,size)==0) + if (str2 && memcmp(data,str2,size)==0) goto equal_ok; data++; } @@ -250,7 +253,7 @@ int masq_ftp_out (struct ip_masq_app *mapp, struct ip_masq *ms, struct sk_buff **skb_p, __u32 maddr) { - struct sk_buff *skb; + struct sk_buff *skb; struct iphdr *iph; struct tcphdr *th; char *p, *data, *data0, *data_limit; @@ -259,40 +262,40 @@ __u16 port; struct ip_masq *n_ms; char buf[25]; /* xxx.xxx.xxx.xxx,ppp,ppp)\000 */ - unsigned flags=0; /* processing flags */ - unsigned buf_len; + unsigned flags=0; /* processing flags */ + unsigned buf_len; int diff=0; /* Only useful for established sessions */ - if (ms->state != IP_MASQ_S_ESTABLISHED) + if (ms->state != IP_MASQ_S_ESTABLISHED) return 0; - skb = *skb_p; + skb = *skb_p; iph = skb->nh.iph; - th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]); - data = (char *)th + (((struct tcphdr*)th)->doff << 2); + th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]); + data = (char *)th + (((struct tcphdr*)th)->doff << 2); data0 = data; - data_limit = skb->h.raw + skb->len; + data_limit = skb->h.raw + skb->len; IP_MASQ_DEBUG(2-debug, "FTP: called masq_ftp_out() for type=0x%x datasize=%d\n", mapp->type, data_limit-data); - /* Only useful for actual data */ + /* Only useful for actual data */ if (data_limit<=data) return 0; /* - * We are about to hack an OUT (from firewall) packet, - * check if - * OUTBOUND: internal client stream - * INBOUND: internal server stream + * We are about to hack an OUT (from firewall) packet, + * check if + * OUTBOUND: internal client stream + * INBOUND: internal server stream */ - + if (IP_MASQ_APP_TYPE2FLAGS(mapp->type)&IP_MASQ_APP_OUTBOUND) { IP_MASQ_DEBUG(1-debug, "FTP: in->out client stream\n"); - /* + /* * Minimum sizes ... * PORT x,x,x,x,y,y+... + \r\n * <---------- 11 + 2 = 13 @@ -307,20 +310,26 @@ } p = data; from = parse_ip_port(&data, &port); - if (masq_ftp_unsafe(from,port)) + from_n = htonl(from); + if (masq_ftp_unsafe(from,port)) return 0; IP_MASQ_DEBUG(1-debug,"FTP: out: PORT %d.%d.%d.%d:%d detected\n", - NIPQUAD(from), port); + NIPQUAD(from_n), port); } else if (IP_MASQ_APP_TYPE2FLAGS(mapp->type)&IP_MASQ_APP_INBOUND) { IP_MASQ_DEBUG(1-debug, "FTP: in->out server stream\n"); + /* + * Exit quickly if no outstanding INBOUND PASV + */ + if (ms->app_data != &masq_in_ftp_pasv) + return 0; - /* + /* * Minimum sizes... - * Entering Passive Mode (x,x,x,x,y,y)+... + \r\n - * <------------------------- 26 + 2 = 28 - * <----------------- 18 + 2 = 20 - * <------------ 13 + 2 = 15 + * Entering Passive Mode (x,x,x,x,y,y)+... + \r\n + * <------------------------- 26 + 2 = 28 + * <----------------- 18 + 2 = 20 + * <------------ 13 + 2 = 15 */ if (!(data=safe_mem_eq2(data, data_limit-_N(28), 8, "ntering ", "NTERING "))) return 0; @@ -337,24 +346,22 @@ from = parse_ip_port(&data, &port); if ((from == 0) || (*data++ !=')')) return 0; + from_n = htonl(from); + flags |= IP_MASQ_FTP_RPAREN; - flags |= IP_MASQ_FTP_RPAREN; - - } else - return 0; + } else + return 0; /* no from detected, give up */ if (from == 0) return 0; - /* store from in network byte order */ - from_n = htonl(from); /* * Now update or create an masquerade entry for it */ - IP_MASQ_DEBUG(1-debug, "FTP: out: %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n", - NIPQUAD(from_n), htons(port), + IP_MASQ_DEBUG(1-debug, "FTP: out: %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d\n", + NIPQUAD(from_n), port, NIPQUAD(iph->daddr), 0); n_ms = ip_masq_out_get(iph->protocol, @@ -362,7 +369,7 @@ iph->daddr, 0); if (!n_ms) { n_ms = ip_masq_new(IPPROTO_TCP, - maddr, 0, + ms->maddr, 0, from_n, htons(port), iph->daddr, 0, IP_MASQ_F_NO_DPORT); @@ -383,7 +390,8 @@ (flags&IP_MASQ_FTP_RPAREN)? ')':0); buf_len = strlen(buf); - IP_MASQ_DEBUG(1-debug, "FTP: new PORT %d.%d.%d.%d:%d\n",NIPQUAD(maddr),port); + IP_MASQ_DEBUG(1-debug, "FTP: new PORT %d.%d.%d.%d:%d\n", + NIPQUAD(n_ms->maddr), port); /* * Calculate required delta-offset to keep TCP happy @@ -405,7 +413,7 @@ *skb_p = ip_masq_skb_replace(skb, GFP_ATOMIC, p, data-p, buf, buf_len); } /* - * Move tunnel to listen state + * Move tunnel to listen state */ ip_masq_listen(n_ms); ip_masq_put(n_ms); @@ -439,52 +447,53 @@ struct iphdr *iph; struct tcphdr *th; char *data, *data_limit; - __u32 to; + __u32 to, to_n; __u32 from_n; __u16 port; + __u16 from_port_n; struct ip_masq *n_ms; /* Only useful for established sessions */ - if (ms->state != IP_MASQ_S_ESTABLISHED) + if (ms->state != IP_MASQ_S_ESTABLISHED) return 0; skb = *skb_p; iph = skb->nh.iph; th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]); - data = (char *)th + (((struct tcphdr*)th)->doff << 2); + data = (char *)th + (((struct tcphdr*)th)->doff << 2); data_limit = skb->h.raw + skb->len; - IP_MASQ_DEBUG(2-debug, "FTP: called masq_ftp_in() for type=0x%x datasize=%d\n", + IP_MASQ_DEBUG(2-debug, "FTP: called masq_ftp_in() for type=0x%x datasize=%d\n", mapp->type, data_limit-data); - /* Only useful for actual data */ + /* Only useful for actual data */ if (data_limit<=data) return 0; /* - * We are about to hack an IN (to firewall) packet, - * check if - * OUTBOUND: internal client stream - * INBOUND: internal server stream + * We are about to hack an IN (to firewall) packet, + * check if + * OUTBOUND: internal client stream + * INBOUND: internal server stream */ if (IP_MASQ_APP_TYPE2FLAGS(mapp->type)&IP_MASQ_APP_OUTBOUND) { IP_MASQ_DEBUG(1-debug, "FTP: out->in client stream\n"); - /* - * For OUTBOUND only parse on input for linking PASV - * data tunnel with control. - * Exit quickly if no outstanding PASV + /* + * For OUTBOUND only parse on input for linking PASV + * data tunnel with control. + * Exit quickly if no outstanding PASV */ if (ms->app_data != &masq_ftp_pasv) return 0; - /* + /* * Minimum sizes... - * Entering Passive Mode (x,x,x,x,y,y)+... + \r\n - * <------------------------- 26 + 2 = 28 - * <----------------- 18 + 2 = 20 - * <------------ 13 + 2 = 15 + * Entering Passive Mode (x,x,x,x,y,y)+... + \r\n + * <------------------------- 26 + 2 = 28 + * <----------------- 18 + 2 = 20 + * <------------ 13 + 2 = 15 */ if (!(data=safe_mem_eq2(data, data_limit-_N(28), 8, "ntering ", "NTERING "))) return 0; @@ -500,26 +509,42 @@ to = parse_ip_port(&data, &port); if (to == 0 || *data != ')') return 0; + to_n = htonl(to); - from_n = ntohl(ms->saddr); - IP_MASQ_DEBUG(1-debug, "FTP: PASV response %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d detected\n", - NIPQUAD(from_n), 0, - NIPQUAD(to), port); + from_n = ms->saddr; + from_port_n = 0; + IP_MASQ_DEBUG(1-debug, "FTP: PASV response %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d detected\n", + NIPQUAD(from_n), 0, + NIPQUAD(to_n), port); } else if (IP_MASQ_APP_TYPE2FLAGS(mapp->type)&IP_MASQ_APP_INBOUND) { + char *data0 = data; + IP_MASQ_DEBUG(1-debug, "FTP: out->in server stream\n"); - if (!(data=safe_mem_eq2(data, data_limit-_N(13), - 5, "PORT ", "port ")) ) + if (!(data=safe_mem_eq2(data, data_limit-_N(13), + 5, "PORT ", "port ")) ) { + if (safe_mem_eq2(data0, data_limit, 6, "PASV\r\n", "pasv\r\n")) { + /* Flags this tunnel as pasv, return */ + ms->app_data = &masq_in_ftp_pasv; + } return 0; + } + to = parse_ip_port(&data, &port); if (to == 0 || (*data != '\r' && *data != '\n')) return 0; + to_n = htonl(to); - from_n = ntohl(ms->saddr); - IP_MASQ_DEBUG(1-debug, "FTP: PORT %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d detected\n", - NIPQUAD(from_n), 0, - NIPQUAD(to), port); + /* RFC 959, Section 3.2: + The server-process default data port is the port adjacent + to the control connection port (i.e., L-1). */ + + from_n = ms->saddr; + from_port_n = htons(ntohs(ms->sport) - 1); + IP_MASQ_DEBUG(1-debug, "FTP: PORT %d.%d.%d.%d:%d -> %d.%d.%d.%d:%d detected\n", + NIPQUAD(from_n), htons(from_port_n), + NIPQUAD(to_n), port); } else return 0; /* @@ -527,14 +552,14 @@ */ n_ms = ip_masq_out_get(iph->protocol, - ms->saddr, 0, - htonl(to), htons(port)); + from_n, from_port_n, + htonl(to), htons(port)); if (!n_ms) { n_ms = ip_masq_new(IPPROTO_TCP, - maddr, 0, - ms->saddr, 0, - htonl(to), htons(port), - IP_MASQ_F_NO_SPORT); + ms->maddr, from_port_n, + from_n, from_port_n, + htonl(to), htons(port), + from_port_n ? 0 : IP_MASQ_F_NO_SPORT); if (n_ms==NULL) return 0; @@ -556,14 +581,14 @@ } struct ip_masq_app ip_masq_ftp = { - NULL, /* next */ + NULL, /* next */ "ftp", /* name */ - 0, /* type */ - 0, /* n_attach */ - masq_ftp_init_1, /* ip_masq_init_1 */ - masq_ftp_done_1, /* ip_masq_done_1 */ - masq_ftp_out, /* pkt_out */ - masq_ftp_in, /* pkt_in */ + 0, /* type */ + 0, /* n_attach */ + masq_ftp_init_1, /* ip_masq_init_1 */ + masq_ftp_done_1, /* ip_masq_done_1 */ + masq_ftp_out, /* pkt_out */ + masq_ftp_in, /* pkt_in */ }; static struct ip_masq_app *make_instance(const struct ip_masq_app *mapp_class, int *err) { @@ -575,9 +600,9 @@ return mapp; } -/* +/* * Register all solicited ports (last array element is - * reserved for firewall "mark" object + * reserved for firewall "mark" object */ static int register_ports(struct ip_masq_app *mapp_instances[], int *ports, unsigned flags) { int i; @@ -587,13 +612,13 @@ if (ports[i]) { if (!(mapp = make_instance(&ip_masq_ftp, &err))) goto end; - if ((err=ip_masq_app_init_proto_port(mapp, flags, IPPROTO_TCP, ports[i]))) + if ((err=ip_masq_app_init_proto_port(mapp, flags, IPPROTO_TCP, ports[i]))) goto end_kfree; if ((err=register_ip_masq_app_type(mapp))) goto end_kfree; mapp_instances[i]=mapp; - IP_MASQ_DEBUG(1-debug, "FTP: loaded support on %sport[%d] = %d, type=0x%x\n", + IP_MASQ_DEBUG(1-debug, "FTP: loaded support on %sport[%d] = %d, type=0x%x\n", (flags&IP_MASQ_APP_INBOUND)? "in_":"", i, ports[i], mapp->type); } else { @@ -609,8 +634,8 @@ return err; } /* - * Unregister ALL ports, _including_ the (possible) firewall - * mark. + * Unregister ALL ports, _including_ the (possible) firewall + * mark. */ static int unregister_ports(struct ip_masq_app *mapp_instances[]) { int i, j, k=0; @@ -630,7 +655,7 @@ } /* - * ip_masq_ftp initialization + * ip_masq_ftp initialization */ __initfunc(int ip_masq_ftp_init(void)) @@ -670,7 +695,7 @@ } /* - * ip_masq_ftp fin. + * ip_masq_ftp fin. */ int ip_masq_ftp_done(void) @@ -686,15 +711,15 @@ int init_module(void) { - if (ip_masq_ftp_init() != 0) - return -EIO; - return 0; + if (ip_masq_ftp_init() != 0) + return -EIO; + return 0; } void cleanup_module(void) { - if (ip_masq_ftp_done() != 0) - printk(KERN_INFO "ip_masq_ftp: can't remove module"); + if (ip_masq_ftp_done() != 0) + printk(KERN_INFO "ip_masq_ftp: can't remove module"); } #endif /* MODULE */