--- ip_fw.c.orig Tue Jan 4 16:12:26 2000 +++ ip_fw.c Fri Apr 21 14:04:32 2000 @@ -37,6 +37,7 @@ * 19-May-1999: Star Wars: The Phantom Menace opened. Rule num * printed in log (modified from Michael Hasenstein's patch). * Added SYN in log message. --RR + * 20-May-1999: Added dynamic ftp-data rules --Michael Hasenstein . * 23-Jul-1999: Fixed small fragment security exposure opened on 15-May-1998. * John McDonald * Thomas Lopatic @@ -110,7 +111,7 @@ * * For SMP (kernel v2.1+), multiply this by # CPUs. * - * [Note that this in not correct for 2.2 - because the socket code always + * [Note that this is not correct for 2.2 - because the socket code always * uses lock_kernel() to serialize, and bottom halves (timers and net_bhs) * only run on one CPU at a time. This will probably change for 2.3. * It is still good to use spinlocks because that avoids the global cli() @@ -218,6 +219,7 @@ struct ip_fwkernel { struct ip_fw ipfw; + struct timer_list timer; /* dynamic rules only */ struct ip_fwkernel *next; /* where to go next if current * rule doesn't match */ struct ip_chain *branch; /* which branch to jump to if @@ -271,6 +273,11 @@ #define IP_FW_FORWARD_CHAIN (ip_fw_chains->next) #define IP_FW_OUTPUT_CHAIN (ip_fw_chains->next->next) +/* for kernel-maintained ftp-data rules */ +static struct ip_chain *ip_ftpdata_chain; +#define IP_FW_FTPDATA_TIMEOUT 3*60*HZ +#define IP_FW_FTPDATA_FIN_TIMEOUT 1*60*HZ + /* Returns 1 if the port is matched by the range, 0 otherwise */ extern inline int port_match(__u16 min, __u16 max, __u16 port, int frag, int invert) @@ -280,6 +287,37 @@ else return (port >= min && port <= max) ^ invert; } +/* + * Returns whether matches rule or not. + * Only checks if a connection is matched. A connection is determined + * by protocol, src-IP, dst-IP, src-port, dst-port + * Returns true, if packet is part of the connection. Doesn't use + * netmasks, of course, and is bi-directional. Everything else, like + * interface, SYN-flag and the like, don't matter - if the packet is part + * of the connection we check for, it is allowed in/out/through (whatever) + */ +static inline int connection_match(struct ip_fwkernel *f, + struct iphdr *ip, + __u16 src_port, __u16 dst_port, + unsigned int slot) +{ + if ((ip->protocol == f->ipfw.fw_proto) && + (((ip->saddr == f->ipfw.fw_src.s_addr) && + (ip->daddr == f->ipfw.fw_dst.s_addr) && + (src_port == f->ipfw.fw_spts[0]) && + (dst_port == f->ipfw.fw_dpts[0])) || + ((ip->saddr == f->ipfw.fw_dst.s_addr) && + (ip->daddr == f->ipfw.fw_src.s_addr) && + (src_port == f->ipfw.fw_dpts[0]) && + (dst_port == f->ipfw.fw_spts[0])))) + { + f->counters[slot].bcnt+=ntohs(ip->tot_len); + f->counters[slot].pcnt++; + return 1; + } else + return 0; +} + /* Returns whether matches rule or not. */ static int ip_rule_match(struct ip_fwkernel *f, const char *ifname, @@ -586,6 +624,286 @@ return 1; } +/* Must have write lock & interrupts off for any of these */ + +/* + * called by expiring timer to delete expired, dynamically + * created rule (by the kernel, e.g. ftp-data rules) + */ +static void kernel_rule_expire(unsigned long data) +{ + struct ip_fwkernel *f = (struct ip_fwkernel *)data; + struct ip_fwkernel *tmp; + + FWC_HAVE_LOCK(fwc_wlocks); + + /*special case: first rule */ + if (ip_ftpdata_chain->chain == f) { + ip_ftpdata_chain->chain = f->next; + kfree_s(f,sizeof(*f)); + return; + } + + for (tmp=ip_ftpdata_chain->chain; tmp; tmp=tmp->next) { + if (tmp->next == f) { + tmp->next = tmp->next->next; + kfree_s(f,sizeof(*f)); + return; + } + } +} + + +/* This function sets all the byte counters in a chain to zero. The + * input is a pointer to the chain required for zeroing */ +static int zero_fw_chain(struct ip_chain *chainptr) +{ + struct ip_fwkernel *i; + + FWC_HAVE_LOCK(fwc_wlocks); + for (i = chainptr->chain; i; i = i->next) + memset(i->counters, 0, sizeof(struct ip_counters)*NUM_SLOTS); + return 0; +} + +static int clear_fw_chain(struct ip_chain *chainptr) +{ + struct ip_fwkernel *i= chainptr->chain; + + FWC_HAVE_LOCK(fwc_wlocks); + chainptr->chain=NULL; + + while (i) { + struct ip_fwkernel *tmp = i->next; + if (i->timer.function == kernel_rule_expire) /* only kernel-created rules have one */ + del_timer(&(i->timer)); + if (i->branch) + i->branch->refcount--; + kfree(i); + i = tmp; + } + return 0; +} + +static int replace_in_chain(struct ip_chain *chainptr, + struct ip_fwkernel *frwl, + __u32 position) +{ + struct ip_fwkernel *f = chainptr->chain; + + FWC_HAVE_LOCK(fwc_wlocks); + + while (--position && f != NULL) f = f->next; + if (f == NULL) + return EINVAL; + + if (f->branch) f->branch->refcount--; + if (frwl->branch) frwl->branch->refcount++; + + frwl->next = f->next; + memcpy(f,frwl,sizeof(struct ip_fwkernel)); + kfree(frwl); + return 0; +} + +static int append_to_chain(struct ip_chain *chainptr, struct ip_fwkernel *rule) +{ + struct ip_fwkernel *i; + + FWC_HAVE_LOCK(fwc_wlocks); + /* Special case if no rules already present */ + if (chainptr->chain == NULL) { + + /* If pointer writes are atomic then turning off + * interupts is not necessary. */ + chainptr->chain = rule; + if (rule->branch) rule->branch->refcount++; + return 0; + } + + /* Find the rule before the end of the chain */ + for (i = chainptr->chain; i->next; i = i->next); + i->next = rule; + if (rule->branch) rule->branch->refcount++; + return 0; +} + +/* This function inserts a rule at the position of position in the + * chain refenced by chainptr. If position is 1 then this rule will + * become the new rule one. */ +static int insert_in_chain(struct ip_chain *chainptr, + struct ip_fwkernel *frwl, + __u32 position) +{ + struct ip_fwkernel *f = chainptr->chain; + + FWC_HAVE_LOCK(fwc_wlocks); + /* special case if the position is number 1 */ + if (position == 1) { + frwl->next = chainptr->chain; + if (frwl->branch) frwl->branch->refcount++; + chainptr->chain = frwl; + return 0; + } + position--; + while (--position && f != NULL) f = f->next; + if (f == NULL) + return EINVAL; + if (frwl->branch) frwl->branch->refcount++; + frwl->next = f->next; + + f->next = frwl; + return 0; +} + +/* This function deletes the a rule from a given rulenum and chain. + * With rulenum = 1 is the first rule is deleted. */ + +static int del_num_from_chain(struct ip_chain *chainptr, __u32 rulenum) +{ + struct ip_fwkernel *i=chainptr->chain,*tmp; + + FWC_HAVE_LOCK(fwc_wlocks); + + if (!chainptr->chain) + return ENOENT; + + /* Need a special case for the first rule */ + if (rulenum == 1) { + /* store temp to allow for freeing up of memory */ + tmp = chainptr->chain; + if (chainptr->chain->branch) chainptr->chain->branch->refcount--; + chainptr->chain = chainptr->chain->next; + kfree(tmp); /* free memory that is now unused */ + } else { + rulenum--; + while (--rulenum && i->next ) i = i->next; + if (!i->next) + return ENOENT; + tmp = i->next; + if (i->next->branch) + i->next->branch->refcount--; + i->next = i->next->next; + kfree(tmp); + } + return 0; +} + + +/* This function deletes a rule from a given rule and chain. + * The rule that is deleted is the first occursance of that rule. */ +static int del_rule_from_chain(struct ip_chain *chainptr, + struct ip_fwkernel *frwl) +{ + struct ip_fwkernel *ltmp,*ftmp = chainptr->chain ; + int was_found; + + FWC_HAVE_LOCK(fwc_wlocks); + + /* Sure, we should compare marks, but since the `ipfwadm' + * script uses it for an unholy hack... well, life is easier + * this way. We also mask it out of the flags word. --PR */ + for (ltmp=NULL, was_found=0; + !was_found && ftmp != NULL; + ltmp = ftmp,ftmp = ftmp->next) { + if (ftmp->ipfw.fw_src.s_addr!=frwl->ipfw.fw_src.s_addr + || ftmp->ipfw.fw_dst.s_addr!=frwl->ipfw.fw_dst.s_addr + || ftmp->ipfw.fw_smsk.s_addr!=frwl->ipfw.fw_smsk.s_addr + || ftmp->ipfw.fw_dmsk.s_addr!=frwl->ipfw.fw_dmsk.s_addr +#if 0 + || ftmp->ipfw.fw_flg!=frwl->ipfw.fw_flg +#else + || ((ftmp->ipfw.fw_flg & ~IP_FW_F_MARKABS) + != (frwl->ipfw.fw_flg & ~IP_FW_F_MARKABS)) +#endif + || ftmp->ipfw.fw_invflg!=frwl->ipfw.fw_invflg + || ftmp->ipfw.fw_proto!=frwl->ipfw.fw_proto +#if 0 + || ftmp->ipfw.fw_mark!=frwl->ipfw.fw_mark +#endif + || ftmp->ipfw.fw_redirpt!=frwl->ipfw.fw_redirpt + || ftmp->ipfw.fw_spts[0]!=frwl->ipfw.fw_spts[0] + || ftmp->ipfw.fw_spts[1]!=frwl->ipfw.fw_spts[1] + || ftmp->ipfw.fw_dpts[0]!=frwl->ipfw.fw_dpts[0] + || ftmp->ipfw.fw_dpts[1]!=frwl->ipfw.fw_dpts[1] + || ftmp->ipfw.fw_outputsize!=frwl->ipfw.fw_outputsize) { + duprintf("del_rule_from_chain: mismatch:" + "src:%u/%u dst:%u/%u smsk:%u/%u dmsk:%u/%u " + "flg:%hX/%hX invflg:%hX/%hX proto:%u/%u " + "mark:%u/%u " + "ports:%hu-%hu/%hu-%hu %hu-%hu/%hu-%hu " + "outputsize:%hu-%hu\n", + ftmp->ipfw.fw_src.s_addr, + frwl->ipfw.fw_src.s_addr, + ftmp->ipfw.fw_dst.s_addr, + frwl->ipfw.fw_dst.s_addr, + ftmp->ipfw.fw_smsk.s_addr, + frwl->ipfw.fw_smsk.s_addr, + ftmp->ipfw.fw_dmsk.s_addr, + frwl->ipfw.fw_dmsk.s_addr, + ftmp->ipfw.fw_flg, + frwl->ipfw.fw_flg, + ftmp->ipfw.fw_invflg, + frwl->ipfw.fw_invflg, + ftmp->ipfw.fw_proto, + frwl->ipfw.fw_proto, + ftmp->ipfw.fw_mark, + frwl->ipfw.fw_mark, + ftmp->ipfw.fw_spts[0], + frwl->ipfw.fw_spts[0], + ftmp->ipfw.fw_spts[1], + frwl->ipfw.fw_spts[1], + ftmp->ipfw.fw_dpts[0], + frwl->ipfw.fw_dpts[0], + ftmp->ipfw.fw_dpts[1], + frwl->ipfw.fw_dpts[1], + ftmp->ipfw.fw_outputsize, + frwl->ipfw.fw_outputsize); + continue; + } + + if (strncmp(ftmp->ipfw.fw_vianame, + frwl->ipfw.fw_vianame, + IFNAMSIZ)) { + duprintf("del_rule_from_chain: if mismatch: %s/%s\n", + ftmp->ipfw.fw_vianame, + frwl->ipfw.fw_vianame); + continue; + } + if (ftmp->branch != frwl->branch) { + duprintf("del_rule_from_chain: branch mismatch: " + "%s/%s\n", + ftmp->branch?ftmp->branch->label:"(null)", + frwl->branch?frwl->branch->label:"(null)"); + continue; + } + if (ftmp->branch == NULL + && ftmp->simplebranch != frwl->simplebranch) { + duprintf("del_rule_from_chain: simplebranch mismatch: " + "%i/%i\n", + ftmp->simplebranch, frwl->simplebranch); + continue; + } + was_found = 1; + if (ftmp->branch) + ftmp->branch->refcount--; + if (ltmp) + ltmp->next = ftmp->next; + else + chainptr->chain = ftmp->next; + kfree(ftmp); + break; + } + + if (was_found) + return 0; + else { + duprintf("del_rule_from_chain: no matching rule found\n"); + return EINVAL; + } +} + + /* * Returns one of the generic firewall policies, like FW_ACCEPT. * @@ -613,6 +931,13 @@ int ret = FW_SKIP+2; unsigned int count; + /* for kernel-generated ftp-data rules */ + char *data, *data_limit; + unsigned char p1,p2,p3,p4,p5,p6; + __u32 from = 0; + __u16 port = 0; + + /* We handle fragments by dealing with the first fragment as * if it was a normal packet. All other fragments are treated * normally, except that they will NEVER match rules that ask @@ -638,7 +963,7 @@ } /* If we can't investigate ports, treat as fragment. It's - * either a trucated whole packet, or a truncated first + * either a truncated whole packet, or a truncated first * fragment, or a TCP first fragment of length 8-15, in which * case the above rule stops reassembly. */ @@ -732,364 +1057,222 @@ #endif if (!testing) FWC_READ_LOCK(&ip_fw_lock); - else FWC_HAVE_LOCK(fwc_rlocks); - - f = chain->chain; - count = 0; - do { - for (; f; f = f->next) { - count++; - if (ip_rule_match(f,rif,ip, - tcpsyn,src_port,dst_port,offset!=0)) { - if (!testing - && !ip_fw_domatch(f, ip, rif, chain->label, - skb, slot, - src_port, dst_port, - count, tcpsyn)) { - ret = FW_BLOCK; - goto out; - } - break; - } - } - if (f) { - if (f->branch) { - /* Do sanity check to see if we have - * already set prevchain and if so we - * must be in a loop */ - if (f->branch->reent[slot].prevchain) { - if (!testing) { - printk(KERN_ERR - "IP firewall: " - "Loop detected " - "at `%s'.\n", - f->branch->label); - cleanup(chain, 1, slot); - ret = FW_BLOCK; - } else { - cleanup(chain, 0, slot); - ret = FW_SKIP+1; - } - } - else { - f->branch->reent[slot].prevchain - = chain; - f->branch->reent[slot].count = count; - f->branch->reent[slot].prevrule - = f->next; - chain = f->branch; - f = chain->chain; - count = 0; - } - } - else if (f->simplebranch == FW_SKIP) - f = f->next; - else if (f->simplebranch == FW_SKIP+1) { - /* Just like falling off the chain */ - goto fall_off_chain; - } - else { - cleanup(chain, 0, slot); - ret = f->simplebranch; - } - } /* f == NULL */ - else { - fall_off_chain: - if (chain->reent[slot].prevchain) { - struct ip_chain *tmp = chain; - f = chain->reent[slot].prevrule; - count = chain->reent[slot].count; - chain = chain->reent[slot].prevchain; - tmp->reent[slot].prevchain = NULL; - } - else { - ret = chain->policy; - if (!testing) { - chain->reent[slot].counters.pcnt++; - chain->reent[slot].counters.bcnt - += ntohs(ip->tot_len); - } - } - } - } while (ret == FW_SKIP+2); - - out: - if (!testing) FWC_READ_UNLOCK(&ip_fw_lock); - - /* Recalculate checksum if not going to reject, and TOS changed. */ - if (ip->tos != oldtos - && ret != FW_REJECT && ret != FW_BLOCK - && !testing) - ip_send_check(ip); - -#ifdef CONFIG_IP_TRANSPARENT_PROXY - if (ret == FW_REDIRECT && redirport) { - if ((*redirport = htons(f->ipfw.fw_redirpt)) == 0) { - /* Wildcard redirection. - * Note that redirport will become - * 0xFFFF for non-TCP/UDP packets. - */ - *redirport = htons(dst_port); - } - } -#endif - -#ifdef DEBUG_ALLOW_ALL - return (testing ? ret : FW_ACCEPT); -#else - return ret; -#endif -} - -/* Must have write lock & interrupts off for any of these */ - -/* This function sets all the byte counters in a chain to zero. The - * input is a pointer to the chain required for zeroing */ -static int zero_fw_chain(struct ip_chain *chainptr) -{ - struct ip_fwkernel *i; - - FWC_HAVE_LOCK(fwc_wlocks); - for (i = chainptr->chain; i; i = i->next) - memset(i->counters, 0, sizeof(struct ip_counters)*NUM_SLOTS); - return 0; -} - -static int clear_fw_chain(struct ip_chain *chainptr) -{ - struct ip_fwkernel *i= chainptr->chain; - - FWC_HAVE_LOCK(fwc_wlocks); - chainptr->chain=NULL; - - while (i) { - struct ip_fwkernel *tmp = i->next; - if (i->branch) - i->branch->refcount--; - kfree(i); - i = tmp; - } - return 0; -} - -static int replace_in_chain(struct ip_chain *chainptr, - struct ip_fwkernel *frwl, - __u32 position) -{ - struct ip_fwkernel *f = chainptr->chain; - - FWC_HAVE_LOCK(fwc_wlocks); - - while (--position && f != NULL) f = f->next; - if (f == NULL) - return EINVAL; - - if (f->branch) f->branch->refcount--; - if (frwl->branch) frwl->branch->refcount++; - - frwl->next = f->next; - memcpy(f,frwl,sizeof(struct ip_fwkernel)); - kfree(frwl); - return 0; -} - -static int append_to_chain(struct ip_chain *chainptr, struct ip_fwkernel *rule) -{ - struct ip_fwkernel *i; - - FWC_HAVE_LOCK(fwc_wlocks); - /* Special case if no rules already present */ - if (chainptr->chain == NULL) { - - /* If pointer writes are atomic then turning off - * interupts is not necessary. */ - chainptr->chain = rule; - if (rule->branch) rule->branch->refcount++; - return 0; - } - - /* Find the rule before the end of the chain */ - for (i = chainptr->chain; i->next; i = i->next); - i->next = rule; - if (rule->branch) rule->branch->refcount++; - return 0; -} - -/* This function inserts a rule at the position of position in the - * chain refenced by chainptr. If position is 1 then this rule will - * become the new rule one. */ -static int insert_in_chain(struct ip_chain *chainptr, - struct ip_fwkernel *frwl, - __u32 position) -{ - struct ip_fwkernel *f = chainptr->chain; - - FWC_HAVE_LOCK(fwc_wlocks); - /* special case if the position is number 1 */ - if (position == 1) { - frwl->next = chainptr->chain; - if (frwl->branch) frwl->branch->refcount++; - chainptr->chain = frwl; - return 0; - } - position--; - while (--position && f != NULL) f = f->next; - if (f == NULL) - return EINVAL; - if (frwl->branch) frwl->branch->refcount++; - frwl->next = f->next; - - f->next = frwl; - return 0; -} + else FWC_HAVE_LOCK(fwc_rlocks); -/* This function deletes the a rule from a given rulenum and chain. - * With rulenum = 1 is the first rule is deleted. */ -static int del_num_from_chain(struct ip_chain *chainptr, __u32 rulenum) -{ - struct ip_fwkernel *i=chainptr->chain,*tmp; + /* now walk the user chains */ + f = chain->chain; + count = 0; + do { + if (chain == ip_ftpdata_chain) { /* kernel mainteined rules - connection matches */ + for (; f; f = f->next) { + count++; + if (connection_match(f,ip,src_port,dst_port,slot)) { + /* + * update timer - unlike user-rules these rules expire + */ + del_timer(&(f->timer)); - FWC_HAVE_LOCK(fwc_wlocks); + /* misusing flag for FIN-seen (we want to see two FINs) */ + if (tcp->fin || tcp->rst) + f->timer.expires = jiffies + IP_FW_FTPDATA_FIN_TIMEOUT; + else + f->timer.expires = jiffies + IP_FW_FTPDATA_TIMEOUT; - if (!chainptr->chain) - return ENOENT; + add_timer(&(f->timer)); + break; + } + } + } else { /* regular user rules */ + for (; f; f = f->next) { + count++; + if (ip_rule_match(f,rif,ip, + tcpsyn,src_port,dst_port,offset!=0)) { + if (!testing && + !ip_fw_domatch(f, ip, rif, chain->label, + skb, slot, + src_port, dst_port, count, tcpsyn)) + { + ret = FW_BLOCK; + goto out; + } + break; + } + } + } - /* Need a special case for the first rule */ - if (rulenum == 1) { - /* store temp to allow for freeing up of memory */ - tmp = chainptr->chain; - if (chainptr->chain->branch) chainptr->chain->branch->refcount--; - chainptr->chain = chainptr->chain->next; - kfree(tmp); /* free memory that is now unused */ + if (f) { + if (f->branch) { + /* Do sanity check to see if we have + * already set prevchain and if so we + * must be in a loop */ + if (f->branch->reent[slot].prevchain) { + if (!testing) { + printk(KERN_ERR + "IP firewall: " + "Loop detected " + "at `%s'.\n", + f->branch->label); + cleanup(chain, 1, slot); + ret = FW_BLOCK; } else { - rulenum--; - while (--rulenum && i->next ) i = i->next; - if (!i->next) - return ENOENT; - tmp = i->next; - if (i->next->branch) - i->next->branch->refcount--; - i->next = i->next->next; - kfree(tmp); + cleanup(chain, 0, slot); + ret = FW_SKIP+1; } - return 0; -} - - -/* This function deletes the a rule from a given rule and chain. - * The rule that is deleted is the first occursance of that rule. */ -static int del_rule_from_chain(struct ip_chain *chainptr, - struct ip_fwkernel *frwl) -{ - struct ip_fwkernel *ltmp,*ftmp = chainptr->chain ; - int was_found; - - FWC_HAVE_LOCK(fwc_wlocks); + } + else { + f->branch->reent[slot].prevchain + = chain; + f->branch->reent[slot].count = count; + f->branch->reent[slot].prevrule + = f->next; + chain = f->branch; + f = chain->chain; + count = 0; + } + } + else if (f->simplebranch == FW_SKIP) + f = f->next; + else if (f->simplebranch == FW_SKIP+1) { + /* Just like falling off the chain */ + goto fall_off_chain; + } + else { + cleanup(chain, 0, slot); + ret = f->simplebranch; + } + } /* f == NULL */ + else { + fall_off_chain: + if (chain->reent[slot].prevchain) { + struct ip_chain *tmp = chain; + f = chain->reent[slot].prevrule; + count = chain->reent[slot].count; + chain = chain->reent[slot].prevchain; + tmp->reent[slot].prevchain = NULL; + } + else { + ret = chain->policy; + if (!testing) { + chain->reent[slot].counters.pcnt++; + chain->reent[slot].counters.bcnt + += ntohs(ip->tot_len); + } + } + } + } while (ret == FW_SKIP+2); - /* Sure, we should compare marks, but since the `ipfwadm' - * script uses it for an unholy hack... well, life is easier - * this way. We also mask it out of the flags word. --PR */ - for (ltmp=NULL, was_found=0; - !was_found && ftmp != NULL; - ltmp = ftmp,ftmp = ftmp->next) { - if (ftmp->ipfw.fw_src.s_addr!=frwl->ipfw.fw_src.s_addr - || ftmp->ipfw.fw_dst.s_addr!=frwl->ipfw.fw_dst.s_addr - || ftmp->ipfw.fw_smsk.s_addr!=frwl->ipfw.fw_smsk.s_addr - || ftmp->ipfw.fw_dmsk.s_addr!=frwl->ipfw.fw_dmsk.s_addr -#if 0 - || ftmp->ipfw.fw_flg!=frwl->ipfw.fw_flg -#else - || ((ftmp->ipfw.fw_flg & ~IP_FW_F_MARKABS) - != (frwl->ipfw.fw_flg & ~IP_FW_F_MARKABS)) -#endif - || ftmp->ipfw.fw_invflg!=frwl->ipfw.fw_invflg - || ftmp->ipfw.fw_proto!=frwl->ipfw.fw_proto -#if 0 - || ftmp->ipfw.fw_mark!=frwl->ipfw.fw_mark -#endif - || ftmp->ipfw.fw_redirpt!=frwl->ipfw.fw_redirpt - || ftmp->ipfw.fw_spts[0]!=frwl->ipfw.fw_spts[0] - || ftmp->ipfw.fw_spts[1]!=frwl->ipfw.fw_spts[1] - || ftmp->ipfw.fw_dpts[0]!=frwl->ipfw.fw_dpts[0] - || ftmp->ipfw.fw_dpts[1]!=frwl->ipfw.fw_dpts[1] - || ftmp->ipfw.fw_outputsize!=frwl->ipfw.fw_outputsize) { - duprintf("del_rule_from_chain: mismatch:" - "src:%u/%u dst:%u/%u smsk:%u/%u dmsk:%u/%u " - "flg:%hX/%hX invflg:%hX/%hX proto:%u/%u " - "mark:%u/%u " - "ports:%hu-%hu/%hu-%hu %hu-%hu/%hu-%hu " - "outputsize:%hu-%hu\n", - ftmp->ipfw.fw_src.s_addr, - frwl->ipfw.fw_src.s_addr, - ftmp->ipfw.fw_dst.s_addr, - frwl->ipfw.fw_dst.s_addr, - ftmp->ipfw.fw_smsk.s_addr, - frwl->ipfw.fw_smsk.s_addr, - ftmp->ipfw.fw_dmsk.s_addr, - frwl->ipfw.fw_dmsk.s_addr, - ftmp->ipfw.fw_flg, - frwl->ipfw.fw_flg, - ftmp->ipfw.fw_invflg, - frwl->ipfw.fw_invflg, - ftmp->ipfw.fw_proto, - frwl->ipfw.fw_proto, - ftmp->ipfw.fw_mark, - frwl->ipfw.fw_mark, - ftmp->ipfw.fw_spts[0], - frwl->ipfw.fw_spts[0], - ftmp->ipfw.fw_spts[1], - frwl->ipfw.fw_spts[1], - ftmp->ipfw.fw_dpts[0], - frwl->ipfw.fw_dpts[0], - ftmp->ipfw.fw_dpts[1], - frwl->ipfw.fw_dpts[1], - ftmp->ipfw.fw_outputsize, - frwl->ipfw.fw_outputsize); + /* for dynamic ftp-date rules: check for PASV or PORT commands */ + if ((chain == IP_FW_OUTPUT_CHAIN) && (ip_ftpdata_chain)) + if ((ip->protocol == IPPROTO_TCP) && (!offset) && (dst_port == 21)) { + data = (char *)( ((__u32*)tcp)+tcp->doff ); + data_limit = skb->h.raw + skb->len - 18; + + /* if there's a PASV command, we must look for the response packet */ + if (skb->len >= 6 && (memcmp(data, "PASV\r\n", 6) == 0 || memcmp(data, "pasv\r\n", 6) == 0)) + f = f; /* do we really need dynamic PASV rules? */ + + /* look for ftp PORT command in the packet, from ip_masq_ftp.c */ + while (data < data_limit) { + if (memcmp(data,"PORT ",5) && memcmp(data,"port ",5)) { + data ++; continue; } - if (strncmp(ftmp->ipfw.fw_vianame, - frwl->ipfw.fw_vianame, - IFNAMSIZ)) { - duprintf("del_rule_from_chain: if mismatch: %s/%s\n", - ftmp->ipfw.fw_vianame, - frwl->ipfw.fw_vianame); + /* extract IP and port */ + p1 = simple_strtoul(data+5,&data,10); + if (*data!=',') continue; - } - if (ftmp->branch != frwl->branch) { - duprintf("del_rule_from_chain: branch mismatch: " - "%s/%s\n", - ftmp->branch?ftmp->branch->label:"(null)", - frwl->branch?frwl->branch->label:"(null)"); + p2 = simple_strtoul(data+1,&data,10); + if (*data!=',') continue; - } - if (ftmp->branch == NULL - && ftmp->simplebranch != frwl->simplebranch) { - duprintf("del_rule_from_chain: simplebranch mismatch: " - "%i/%i\n", - ftmp->simplebranch, frwl->simplebranch); + p3 = simple_strtoul(data+1,&data,10); + if (*data!=',') continue; + p4 = simple_strtoul(data+1,&data,10); + if (*data!=',') + continue; + p5 = simple_strtoul(data+1,&data,10); + if (*data!=',') + continue; + p6 = simple_strtoul(data+1,&data,10); + if (*data!='\r' && *data!='\n') + continue; + + from = (p1<<24) | (p2<<16) | (p3<<8) | p4; + port = (p5<<8) | p6; + + /* add a rule to chain ftp-data */ + if (!(f = kmalloc(SIZEOF_STRUCT_IP_FW_KERNEL, GFP_ATOMIC))) { + printk("ipchains: couldn't malloc space for dynamic ftp-data rule\n"); + } else { + f->ipfw.fw_spts[0] = port; + f->ipfw.fw_dpts[0] = 20; + f->ipfw.fw_src.s_addr = htonl(from); + f->ipfw.fw_dst.s_addr = dst; + f->ipfw.fw_proto = IPPROTO_TCP; + init_timer(&(f->timer)); + f->timer.data = (unsigned long)f; + f->timer.function = kernel_rule_expire; + f->timer.expires = jiffies + IP_FW_FTPDATA_TIMEOUT; + add_timer(&(f->timer)); + + /* + * doesn't matter anyway, since we use + * connection_match(), this is for beautifying + * "ipchains -L" output + */ + f->ipfw.fw_spts[1] = port; + f->ipfw.fw_dpts[1] = 20; + f->ipfw.fw_smsk.s_addr = 0xFFFFFFFF; + f->ipfw.fw_dmsk.s_addr = 0xFFFFFFFF; + f->ipfw.fw_flg = 0; + f->ipfw.fw_invflg = 0; + f->ipfw.fw_redirpt = 0; + f->ipfw.fw_tosand = 0xFF; + f->ipfw.fw_tosxor = 0; + f->ipfw.fw_mark = 0; + f->ipfw.fw_outputsize = 0; + strcpy(f->ipfw.fw_vianame, rif); + memset(f->counters, 0, sizeof(struct ip_counters)*NUM_SLOTS); + f->branch = NULL; + f->simplebranch = FW_ACCEPT; + + /* insert as first rule, sort of very primitive LRU... */ + insert_in_chain(ip_ftpdata_chain, f, 1); } - was_found = 1; - if (ftmp->branch) - ftmp->branch->refcount--; - if (ltmp) - ltmp->next = ftmp->next; - else - chainptr->chain = ftmp->next; - kfree(ftmp); - break; } + } /* END checking for ftp PORT command */ - if (was_found) - return 0; - else { - duprintf("del_rule_from_chain: no matching rule found\n"); - return EINVAL; + out: + if (!testing) FWC_READ_UNLOCK(&ip_fw_lock); + + /* Recalculate checksum if not going to reject, and TOS changed. */ + if (ip->tos != oldtos + && ret != FW_REJECT && ret != FW_BLOCK + && !testing) + ip_send_check(ip); + +#ifdef CONFIG_IP_TRANSPARENT_PROXY + if (ret == FW_REDIRECT && redirport) { + if ((*redirport = htons(f->ipfw.fw_redirpt)) == 0) { + /* Wildcard redirection. + * Note that redirport will become + * 0xFFFF for non-TCP/UDP packets. + */ + *redirport = htons(dst_port); + } } +#endif + +#ifdef DEBUG_ALLOW_ALL + return (testing ? ret : FW_ACCEPT); +#else + return ret; +#endif } + /* This function takes the label of a chain and deletes the first * chain with that name. No special cases required for the built in * chains as they have their refcount initilised to 1 so that they are @@ -1103,6 +1286,10 @@ if (strcmp(label, ip_fw_chains->label) == 0) return EBUSY; + /* dynamic ftp-data rules */ + if (!strncmp(label,"ftp-data",8)) + ip_ftpdata_chain = NULL; + for (tmp = ip_fw_chains; tmp->next; tmp = tmp->next) if(strcmp(tmp->next->label,label) == 0) break; @@ -1122,7 +1309,7 @@ return 0; } -/* This is a function to initilise a chain. Built in rules start with +/* This is a function to initialize a chain. Built in rules start with * refcount = 1 so that they cannot be deleted. User defined rules * start with refcount = 0 so they can be deleted. */ static struct ip_chain *ip_init_chain(ip_chainlabel name, @@ -1171,6 +1358,11 @@ * user defined chain * * and therefore can be * deleted */ + + /* dynamic ftp-data rules */ + if (!strncmp(label,"ftp-data",8)) + ip_ftpdata_chain = tmp->next; + return 0; } @@ -1184,7 +1376,7 @@ return 0; } -/* This function takes an ip_fwuser and converts it to a ip_fwkernel. It also +/* This function takes an ip_fwuser and converts it to an ip_fwkernel. It also * performs some checks in the structure. */ static struct ip_fwkernel *convert_ipfw(struct ip_fwuser *fwuser, int *errno) { @@ -1400,6 +1592,8 @@ ret = EINVAL; else if ((chain = find_label(new->fwn_label)) == NULL) ret = ENOENT; + else if (!strncmp(new->fwn_label,"ftp-data",8)) + ret = EINVAL; else if ((ip_fwkern = convert_ipfw(&new->fwn_rule, &ret)) != NULL) ret = replace_in_chain(chain, ip_fwkern, @@ -1416,6 +1610,8 @@ ret = EINVAL; else if ((chain = find_label(new->fwc_label)) == NULL) ret = ENOENT; + else if (!strncmp(new->fwc_label,"ftp-data",8)) + ret = EINVAL; else if ((ip_fwkern = convert_ipfw(&new->fwc_rule, &ret)) != NULL) ret = append_to_chain(chain, ip_fwkern); @@ -1431,6 +1627,8 @@ ret = EINVAL; else if ((chain = find_label(new->fwn_label)) == NULL) ret = ENOENT; + else if (!strncmp(new->fwn_label,"ftp-data",8)) + ret = EINVAL; else if ((ip_fwkern = convert_ipfw(&new->fwn_rule, &ret)) != NULL) ret = insert_in_chain(chain, ip_fwkern, @@ -1447,6 +1645,8 @@ ret = EINVAL; else if ((chain = find_label(new->fwc_label)) == NULL) ret = ENOENT; + else if (!strncmp(new->fwc_label,"ftp-data",8)) + ret = EINVAL; else if ((ip_fwkern = convert_ipfw(&new->fwc_rule, &ret)) != NULL) { ret = del_rule_from_chain(chain, ip_fwkern); @@ -1463,6 +1663,8 @@ ret = EINVAL; else if ((chain = find_label(new->fwd_label)) == NULL) ret = ENOENT; + else if (!strncmp(new->fwd_label,"ftp-data",8)) + ret = EINVAL; else ret = del_num_from_chain(chain, new->fwd_rulenum); } break; @@ -1493,6 +1695,8 @@ ret = EINVAL; else if ((chain = find_label(new->fwp_label)) == NULL) ret = ENOENT; + else if (!strncmp(new->fwp_label,"ftp-data",8)) + ret = EINVAL; else if (chain != IP_FW_INPUT_CHAIN && chain != IP_FW_FORWARD_CHAIN && chain != IP_FW_OUTPUT_CHAIN) { @@ -1753,6 +1957,9 @@ IP_FW_INPUT_CHAIN = ip_init_chain(IP_FW_LABEL_INPUT, 1, FW_ACCEPT); IP_FW_FORWARD_CHAIN = ip_init_chain(IP_FW_LABEL_FORWARD, 1, FW_ACCEPT); IP_FW_OUTPUT_CHAIN = ip_init_chain(IP_FW_LABEL_OUTPUT, 1, FW_ACCEPT); + + /* we start with dynamic ftp rules disabled */ + ip_ftpdata_chain = NULL; if(register_firewall(PF_INET,&ipfw_ops)<0) panic("Unable to register IP firewall.\n");