SOURCES: kernel-nf_rtsp.patch (NEW) - raw http://mike.it-loops.com...

zbyniu zbyniu at pld-linux.org
Thu Mar 13 20:33:34 CET 2008


Author: zbyniu                       Date: Thu Mar 13 19:33:34 2008 GMT
Module: SOURCES                       Tag: HEAD
---- Log message:
- raw http://mike.it-loops.com/rtsp/rtsp-2.6.24.patch

---- Files affected:
SOURCES:
   kernel-nf_rtsp.patch (NONE -> 1.1)  (NEW)

---- Diffs:

================================================================
Index: SOURCES/kernel-nf_rtsp.patch
diff -u /dev/null SOURCES/kernel-nf_rtsp.patch:1.1
--- /dev/null	Thu Mar 13 20:33:34 2008
+++ SOURCES/kernel-nf_rtsp.patch	Thu Mar 13 20:33:29 2008
@@ -0,0 +1,1366 @@
+diff -purN linux-2.6.23-rc2.orig/include/linux/netfilter_helpers.h linux-2.6.23-rc2/include/linux/netfilter_helpers.h
+--- linux-2.6.23-rc2.orig/include/linux/netfilter_helpers.h	1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.23-rc2/include/linux/netfilter_helpers.h	2007-08-05 10:53:55.000000000 +0200
+@@ -0,0 +1,133 @@
++/*
++ * Helpers for netfiler modules.  This file provides implementations for basic
++ * functions such as strncasecmp(), etc.
++ *
++ * gcc will warn for defined but unused functions, so we only include the
++ * functions requested.  The following macros are used:
++ *   NF_NEED_STRNCASECMP        nf_strncasecmp()
++ *   NF_NEED_STRTOU16           nf_strtou16()
++ *   NF_NEED_STRTOU32           nf_strtou32()
++ */
++#ifndef _NETFILTER_HELPERS_H
++#define _NETFILTER_HELPERS_H
++
++/* Only include these functions for kernel code. */
++#ifdef __KERNEL__
++
++#include <linux/ctype.h>
++#define iseol(c) ( (c) == '\r' || (c) == '\n' )
++
++/*
++ * The standard strncasecmp()
++ */
++#ifdef NF_NEED_STRNCASECMP
++static int
++nf_strncasecmp(const char* s1, const char* s2, u_int32_t len)
++{
++    if (s1 == NULL || s2 == NULL)
++    {
++        if (s1 == NULL && s2 == NULL)
++        {
++            return 0;
++        }
++        return (s1 == NULL) ? -1 : 1;
++    }
++    while (len > 0 && tolower(*s1) == tolower(*s2))
++    {
++        len--;
++        s1++;
++        s2++;
++    }
++    return ( (len == 0) ? 0 : (tolower(*s1) - tolower(*s2)) );
++}
++#endif /* NF_NEED_STRNCASECMP */
++
++/*
++ * Parse a string containing a 16-bit unsigned integer.
++ * Returns the number of chars used, or zero if no number is found.
++ */
++#ifdef NF_NEED_STRTOU16
++static int
++nf_strtou16(const char* pbuf, u_int16_t* pval)
++{
++    int n = 0;
++
++    *pval = 0;
++    while (isdigit(pbuf[n]))
++    {
++        *pval = (*pval * 10) + (pbuf[n] - '0');
++        n++;
++    }
++
++    return n;
++}
++#endif /* NF_NEED_STRTOU16 */
++
++/*
++ * Parse a string containing a 32-bit unsigned integer.
++ * Returns the number of chars used, or zero if no number is found.
++ */
++#ifdef NF_NEED_STRTOU32
++static int
++nf_strtou32(const char* pbuf, u_int32_t* pval)
++{
++    int n = 0;
++
++    *pval = 0;
++    while (pbuf[n] >= '0' && pbuf[n] <= '9')
++    {
++        *pval = (*pval * 10) + (pbuf[n] - '0');
++        n++;
++    }
++
++    return n;
++}
++#endif /* NF_NEED_STRTOU32 */
++
++/*
++ * Given a buffer and length, advance to the next line and mark the current
++ * line.
++ */
++#ifdef NF_NEED_NEXTLINE
++static int
++nf_nextline(char* p, uint len, uint* poff, uint* plineoff, uint* plinelen)
++{
++    uint    off = *poff;
++    uint    physlen = 0;
++
++    if (off >= len)
++    {
++        return 0;
++    }
++
++    while (p[off] != '\n')
++    {
++        if (len-off <= 1)
++        {
++            return 0;
++        }
++
++        physlen++;
++        off++;
++    }
++
++    /* if we saw a crlf, physlen needs adjusted */
++    if (physlen > 0 && p[off] == '\n' && p[off-1] == '\r')
++    {
++        physlen--;
++    }
++
++    /* advance past the newline */
++    off++;
++
++    *plineoff = *poff;
++    *plinelen = physlen;
++    *poff = off;
++
++    return 1;
++}
++#endif /* NF_NEED_NEXTLINE */
++
++#endif /* __KERNEL__ */
++
++#endif /* _NETFILTER_HELPERS_H */
+diff -purN linux-2.6.23-rc2.orig/include/linux/netfilter_mime.h linux-2.6.23-rc2/include/linux/netfilter_mime.h
+--- linux-2.6.23-rc2.orig/include/linux/netfilter_mime.h	1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.23-rc2/include/linux/netfilter_mime.h	2007-08-05 10:53:55.000000000 +0200
+@@ -0,0 +1,89 @@
++/*
++ * MIME functions for netfilter modules.  This file provides implementations
++ * for basic MIME parsing.  MIME headers are used in many protocols, such as
++ * HTTP, RTSP, SIP, etc.
++ *
++ * gcc will warn for defined but unused functions, so we only include the
++ * functions requested.  The following macros are used:
++ *   NF_NEED_MIME_NEXTLINE      nf_mime_nextline()
++ */
++#ifndef _NETFILTER_MIME_H
++#define _NETFILTER_MIME_H
++
++/* Only include these functions for kernel code. */
++#ifdef __KERNEL__
++
++#include <linux/ctype.h>
++
++/*
++ * Given a buffer and length, advance to the next line and mark the current
++ * line.  If the current line is empty, *plinelen will be set to zero.  If
++ * not, it will be set to the actual line length (including CRLF).
++ *
++ * 'line' in this context means logical line (includes LWS continuations).
++ * Returns 1 on success, 0 on failure.
++ */
++#ifdef NF_NEED_MIME_NEXTLINE
++static int
++nf_mime_nextline(char* p, uint len, uint* poff, uint* plineoff, uint* plinelen)
++{
++    uint    off = *poff;
++    uint    physlen = 0;
++    int     is_first_line = 1;
++
++    if (off >= len)
++    {
++        return 0;
++    }
++
++    do
++    {
++        while (p[off] != '\n')
++        {
++            if (len-off <= 1)
++            {
++                return 0;
++            }
++
++            physlen++;
++            off++;
++        }
++
++        /* if we saw a crlf, physlen needs adjusted */
++        if (physlen > 0 && p[off] == '\n' && p[off-1] == '\r')
++        {
++            physlen--;
++        }
++
++        /* advance past the newline */
++        off++;
++
++        /* check for an empty line */
++        if (physlen == 0)
++        {
++            break;
++        }
++
++        /* check for colon on the first physical line */
++        if (is_first_line)
++        {
++            is_first_line = 0;
++            if (memchr(p+(*poff), ':', physlen) == NULL)
++            {
++                return 0;
++            }
++        }
++    }
++    while (p[off] == ' ' || p[off] == '\t');
++
++    *plineoff = *poff;
++    *plinelen = (physlen == 0) ? 0 : (off - *poff);
++    *poff = off;
++
++    return 1;
++}
++#endif /* NF_NEED_MIME_NEXTLINE */
++
++#endif /* __KERNEL__ */
++
++#endif /* _NETFILTER_MIME_H */
+diff -purN linux-2.6.23-rc2.orig/net/ipv4/netfilter/Kconfig linux-2.6.23-rc2/net/ipv4/netfilter/Kconfig
+--- linux-2.6.23-rc2.orig/net/ipv4/netfilter/Kconfig	2007-08-05 21:17:01.000000000 +0200
++++ linux-2.6.23-rc2/net/ipv4/netfilter/Kconfig	2007-08-05 11:04:59.000000000 +0200
+@@ -274,6 +274,11 @@ config NF_NAT_IRC
+ 	depends on IP_NF_IPTABLES && NF_CONNTRACK && NF_NAT
+ 	default NF_NAT && NF_CONNTRACK_IRC
+ 
++config NF_NAT_RTSP
++	tristate
++ 	depends on IP_NF_IPTABLES && NF_CONNTRACK && NF_NAT
++ 	default NF_NAT && NF_CONNTRACK_RTSP
++
+ config NF_NAT_TFTP
+ 	tristate
+ 	depends on IP_NF_IPTABLES && NF_CONNTRACK && NF_NAT
+diff -purN linux-2.6.23-rc2.orig/net/ipv4/netfilter/Makefile linux-2.6.23-rc2/net/ipv4/netfilter/Makefile
+--- linux-2.6.23-rc2.orig/net/ipv4/netfilter/Makefile	2007-08-05 21:14:19.000000000 +0200
++++ linux-2.6.23-rc2/net/ipv4/netfilter/Makefile	2007-08-05 10:53:55.000000000 +0200
+@@ -23,6 +23,7 @@ obj-$(CONFIG_NF_NAT_AMANDA) += nf_nat_am
+ obj-$(CONFIG_NF_NAT_FTP) += nf_nat_ftp.o
+ obj-$(CONFIG_NF_NAT_H323) += nf_nat_h323.o
+ obj-$(CONFIG_NF_NAT_IRC) += nf_nat_irc.o
++obj-$(CONFIG_NF_NAT_RTSP) += nf_nat_rtsp.o
+ obj-$(CONFIG_NF_NAT_PPTP) += nf_nat_pptp.o
+ obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o
+ obj-$(CONFIG_NF_NAT_SNMP_BASIC) += nf_nat_snmp_basic.o
+diff -purN linux-2.6.23-rc2.orig/net/netfilter/Kconfig linux-2.6.23-rc2/net/netfilter/Kconfig
+--- linux-2.6.23-rc2.orig/net/netfilter/Kconfig	2007-08-05 21:17:02.000000000 +0200
++++ linux-2.6.23-rc2/net/netfilter/Kconfig	2007-08-05 11:04:59.000000000 +0200
+@@ -249,6 +249,16 @@ config NF_CONNTRACK_TFTP
+ 
+ 	  To compile it as a module, choose M here.  If unsure, say N.
+ 
++config NF_CONNTRACK_RTSP
++	tristate "RTSP protocol support"
++	depends on NF_CONNTRACK
++	help
++		Support the RTSP protocol.  This allows UDP transports to be setup
++		properly, including RTP and RDT.
++
++		If you want to compile it as a module, say 'M' here and read
++		Documentation/modules.txt.  If unsure, say 'Y'.
++
+ config NF_CT_NETLINK
+ 	tristate 'Connection tracking netlink interface (EXPERIMENTAL)'
+ 	depends on EXPERIMENTAL && NF_CONNTRACK && NETFILTER_NETLINK
+diff -purN linux-2.6.23-rc2.orig/net/netfilter/Makefile linux-2.6.23-rc2/net/netfilter/Makefile
+--- linux-2.6.23-rc2.orig/net/netfilter/Makefile	2007-08-05 21:17:02.000000000 +0200
++++ linux-2.6.23-rc2/net/netfilter/Makefile	2007-08-05 11:04:59.000000000 +0200
+@@ -33,6 +33,7 @@ obj-$(CONFIG_NF_CONNTRACK_PPTP) += nf_co
+ obj-$(CONFIG_NF_CONNTRACK_SANE) += nf_conntrack_sane.o
+ obj-$(CONFIG_NF_CONNTRACK_SIP) += nf_conntrack_sip.o
+ obj-$(CONFIG_NF_CONNTRACK_TFTP) += nf_conntrack_tftp.o
++obj-$(CONFIG_NF_CONNTRACK_RTSP) += nf_conntrack_rtsp.o
+ 
+ # generic X tables 
+ obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o
+diff -purN linux-2.6.24-rc1.orig/include/linux/netfilter/nf_conntrack_rtsp.h linux-2.6.24-rc1/include/linux/netfilter/nf_conntrack_rtsp.h
+--- linux-2.6.24-rc1.orig/include/linux/netfilter/nf_conntrack_rtsp.h	1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.24-rc1/include/linux/netfilter/nf_conntrack_rtsp.h	2007-11-10 17:16:36.000000000 +0100
+@@ -0,0 +1,63 @@
++/*
++ * RTSP extension for IP connection tracking.
++ * (C) 2003 by Tom Marshall <tmarshall at real.com>
++ * based on ip_conntrack_irc.h
++ *
++ *      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, or (at your option) any later version.
++ */
++#ifndef _IP_CONNTRACK_RTSP_H
++#define _IP_CONNTRACK_RTSP_H
++
++//#define IP_NF_RTSP_DEBUG 1
++#define IP_NF_RTSP_VERSION "0.6.21"
++
++#ifdef __KERNEL__
++/* port block types */
++typedef enum {
++    pb_single,  /* client_port=x */
++    pb_range,   /* client_port=x-y */
++    pb_discon   /* client_port=x/y (rtspbis) */
++} portblock_t;
++
++/* We record seq number and length of rtsp headers here, all in host order. */
++
++/*
++ * This structure is per expected connection.  It is a member of struct
++ * ip_conntrack_expect.  The TCP SEQ for the conntrack expect is stored
++ * there and we are expected to only store the length of the data which
++ * needs replaced.  If a packet contains multiple RTSP messages, we create
++ * one expected connection per message.
++ *
++ * We use these variables to mark the entire header block.  This may seem
++ * like overkill, but the nature of RTSP requires it.  A header may appear
++ * multiple times in a message.  We must treat two Transport headers the
++ * same as one Transport header with two entries.
++ */
++struct ip_ct_rtsp_expect
++{
++    u_int32_t   len;        /* length of header block */
++    portblock_t pbtype;     /* Type of port block that was requested */
++    u_int16_t   loport;     /* Port that was requested, low or first */
++    u_int16_t   hiport;     /* Port that was requested, high or second */
++#if 0
++    uint        method;     /* RTSP method */
++    uint        cseq;       /* CSeq from request */
++#endif
++};
++
++extern unsigned int (*nf_nat_rtsp_hook)(struct sk_buff *skb,
++				 enum ip_conntrack_info ctinfo,
++				 unsigned int matchoff, unsigned int matchlen,
++				 struct ip_ct_rtsp_expect *prtspexp,
++				 struct nf_conntrack_expect *exp);
++
++extern void (*nf_nat_rtsp_hook_expectfn)(struct nf_conn *ct, struct nf_conntrack_expect *exp);
++
++#define RTSP_PORT   554
++
++#endif /* __KERNEL__ */
++
++#endif /* _IP_CONNTRACK_RTSP_H */
+diff -purN linux-2.6.24-rc1.orig/net/ipv4/netfilter/nf_nat_rtsp.c linux-2.6.24-rc1/net/ipv4/netfilter/nf_nat_rtsp.c
+--- linux-2.6.24-rc1.orig/net/ipv4/netfilter/nf_nat_rtsp.c	1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.24-rc1/net/ipv4/netfilter/nf_nat_rtsp.c	2007-11-10 17:17:21.000000000 +0100
+@@ -0,0 +1,489 @@
++/*
++ * RTSP extension for TCP NAT alteration
++ * (C) 2003 by Tom Marshall <tmarshall at real.com>
++ * based on ip_nat_irc.c
++ *
++ *      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, or (at your option) any later version.
++ *
++ * Module load syntax:
++ *      insmod nf_nat_rtsp.o ports=port1,port2,...port<MAX_PORTS>
++ *                           stunaddr=<address>
++ *                           destaction=[auto|strip|none]
++ *
++ * If no ports are specified, the default will be port 554 only.
++ *
++ * stunaddr specifies the address used to detect that a client is using STUN.
++ * If this address is seen in the destination parameter, it is assumed that
++ * the client has already punched a UDP hole in the firewall, so we don't
++ * mangle the client_port.  If none is specified, it is autodetected.  It
++ * only needs to be set if you have multiple levels of NAT.  It should be
++ * set to the external address that the STUN clients detect.  Note that in
++ * this case, it will not be possible for clients to use UDP with servers
++ * between the NATs.
++ *
++ * If no destaction is specified, auto is used.
++ *   destaction=auto:  strip destination parameter if it is not stunaddr.
++ *   destaction=strip: always strip destination parameter (not recommended).
++ *   destaction=none:  do not touch destination parameter (not recommended).
++ */
++
++#include <linux/module.h>
++#include <net/tcp.h>
++#include <net/netfilter/nf_nat_helper.h>
++#include <net/netfilter/nf_nat_rule.h>
++#include <linux/netfilter/nf_conntrack_rtsp.h>
++#include <net/netfilter/nf_conntrack_expect.h>
++
++#include <linux/inet.h>
++#include <linux/ctype.h>
++#define NF_NEED_STRNCASECMP
++#define NF_NEED_STRTOU16
++#include <linux/netfilter_helpers.h>
++#define NF_NEED_MIME_NEXTLINE
++#include <linux/netfilter_mime.h>
++
++#define MAX_PORTS       8
++#define DSTACT_AUTO     0
++#define DSTACT_STRIP    1
++#define DSTACT_NONE     2
++
++static char*    stunaddr = NULL;
++static char*    destaction = NULL;
++
++static u_int32_t extip = 0;
++static int       dstact = 0;
++
++MODULE_AUTHOR("Tom Marshall <tmarshall at real.com>");
++MODULE_DESCRIPTION("RTSP network address translation module");
++MODULE_LICENSE("GPL");
++module_param(stunaddr, charp, 0644);
++MODULE_PARM_DESC(stunaddr, "Address for detecting STUN");
++module_param(destaction, charp, 0644);
++MODULE_PARM_DESC(destaction, "Action for destination parameter (auto/strip/none)");
++
++#define SKIP_WSPACE(ptr,len,off) while(off < len && isspace(*(ptr+off))) { off++; }
++
++/*** helper functions ***/
++
++static void
++get_skb_tcpdata(struct sk_buff* skb, char** pptcpdata, uint* ptcpdatalen)
++{
++    struct iphdr*   iph  = ip_hdr(skb);
++    struct tcphdr*  tcph = (void *)iph + ip_hdrlen(skb);
++
++    *pptcpdata = (char*)tcph +  tcph->doff*4;
++    *ptcpdatalen = ((char*)skb_transport_header(skb) + skb->len) - *pptcpdata;
++}
++
++/*** nat functions ***/
++
++/*
++ * Mangle the "Transport:" header:
++ *   - Replace all occurences of "client_port=<spec>"
++ *   - Handle destination parameter
++ *
++ * In:
++ *   ct, ctinfo = conntrack context
++ *   skb        = packet
++ *   tranoff    = Transport header offset from TCP data
++ *   tranlen    = Transport header length (incl. CRLF)
++ *   rport_lo   = replacement low  port (host endian)
++ *   rport_hi   = replacement high port (host endian)
++ *
++ * Returns packet size difference.
++ *
++ * Assumes that a complete transport header is present, ending with CR or LF
++ */
++static int
++rtsp_mangle_tran(enum ip_conntrack_info ctinfo,
++                 struct nf_conntrack_expect* exp,
++								 struct ip_ct_rtsp_expect* prtspexp,
++                 struct sk_buff* skb, uint tranoff, uint tranlen)
++{
++    char*       ptcp;
++    uint        tcplen;
++    char*       ptran;
++    char        rbuf1[16];      /* Replacement buffer (one port) */
++    uint        rbuf1len;       /* Replacement len (one port) */
++    char        rbufa[16];      /* Replacement buffer (all ports) */
++    uint        rbufalen;       /* Replacement len (all ports) */
++    u_int32_t   newip;
++    u_int16_t   loport, hiport;
++    uint        off = 0;
++    uint        diff;           /* Number of bytes we removed */
++
++    struct nf_conn *ct = exp->master;
++    struct nf_conntrack_tuple *t;
++
++    char    szextaddr[15+1];
++    uint    extaddrlen;
++    int     is_stun;
++
++    get_skb_tcpdata(skb, &ptcp, &tcplen);
++    ptran = ptcp+tranoff;
++
++    if (tranoff+tranlen > tcplen || tcplen-tranoff < tranlen ||
++        tranlen < 10 || !iseol(ptran[tranlen-1]) ||
++        nf_strncasecmp(ptran, "Transport:", 10) != 0)
++    {
++        pr_info("sanity check failed\n");
++        return 0;
++    }
++    off += 10;
++    SKIP_WSPACE(ptcp+tranoff, tranlen, off);
++
++    newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip;
++    t = &exp->tuple;
++    t->dst.u3.ip = newip;
++
++    extaddrlen = extip ? sprintf(szextaddr, "%u.%u.%u.%u", NIPQUAD(extip))
++                       : sprintf(szextaddr, "%u.%u.%u.%u", NIPQUAD(newip));
++    pr_debug("stunaddr=%s (%s)\n", szextaddr, (extip?"forced":"auto"));
++
++    rbuf1len = rbufalen = 0;
++    switch (prtspexp->pbtype)
++    {
++    case pb_single:
++        for (loport = prtspexp->loport; loport != 0; loport++) /* XXX: improper wrap? */
++        {
++            t->dst.u.udp.port = htons(loport);
++            if (nf_ct_expect_related(exp) == 0)
++            {
++                pr_debug("using port %hu\n", loport);
++                break;
++            }
++        }
++        if (loport != 0)
++        {
++            rbuf1len = sprintf(rbuf1, "%hu", loport);
++            rbufalen = sprintf(rbufa, "%hu", loport);
++        }
++        break;
++    case pb_range:
++        for (loport = prtspexp->loport; loport != 0; loport += 2) /* XXX: improper wrap? */
++        {
++            t->dst.u.udp.port = htons(loport);
++            if (nf_ct_expect_related(exp) == 0)
++            {
++                hiport = loport + 1; //~exp->mask.dst.u.udp.port;
++                pr_debug("using ports %hu-%hu\n", loport, hiport);
++                break;
++            }
++        }
++        if (loport != 0)
++        {
++            rbuf1len = sprintf(rbuf1, "%hu", loport);
++            rbufalen = sprintf(rbufa, "%hu-%hu", loport, loport+1);
++        }
++        break;
++    case pb_discon:
++        for (loport = prtspexp->loport; loport != 0; loport++) /* XXX: improper wrap? */
++        {
++            t->dst.u.udp.port = htons(loport);
++            if (nf_ct_expect_related(exp) == 0)
++            {
++                pr_debug("using port %hu (1 of 2)\n", loport);
++                break;
++            }
++        }
++        for (hiport = prtspexp->hiport; hiport != 0; hiport++) /* XXX: improper wrap? */
++        {
++            t->dst.u.udp.port = htons(hiport);
++            if (nf_ct_expect_related(exp) == 0)
++            {
++                pr_debug("using port %hu (2 of 2)\n", hiport);
++                break;
++            }
++        }
++        if (loport != 0 && hiport != 0)
++        {
++            rbuf1len = sprintf(rbuf1, "%hu", loport);
++            if (hiport == loport+1)
++            {
++                rbufalen = sprintf(rbufa, "%hu-%hu", loport, hiport);
++            }
++            else
++            {
++                rbufalen = sprintf(rbufa, "%hu/%hu", loport, hiport);
++            }
++        }
++        break;
++    }
++
++    if (rbuf1len == 0)
++    {
++        return 0;   /* cannot get replacement port(s) */
++    }
++
++    /* Transport: tran;field;field=val,tran;field;field=val,... */
++    while (off < tranlen)
++    {
++        uint        saveoff;
++        const char* pparamend;
++        uint        nextparamoff;
++
++        pparamend = memchr(ptran+off, ',', tranlen-off);
++        pparamend = (pparamend == NULL) ? ptran+tranlen : pparamend+1;
++        nextparamoff = pparamend-ptcp;
++
++        /*
++         * We pass over each param twice.  On the first pass, we look for a
<<Diff was trimmed, longer than 597 lines>>


More information about the pld-cvs-commit mailing list