#       enkeys - extract keys from entry
#
# Copyright (c) 1993, 1994, 1995, 1996, 1997, 1998, 1999 by RIPE NCC
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of the author not be
# used in advertising or publicity pertaining to distribution of the
# software without specific, written prior permission.
#
# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
# ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
# AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# $Id: enkeys.pl,v 2.5 1999/10/06 16:41:07 marek Exp $
#
#	$RCSfile: enkeys.pl,v $
#	$Revision: 2.5 $
#	$Author: marek $
#	$Date: 1999/10/06 16:41:07 $
#

#
# notes:
#
# - this is tricky code since it is optimized for speed
#   order is often important and initializing is really minimized
# - $tags="" is a valid type in enkeys !!!
#   we use this one for lookups (makekeys) when we know the search string
#   but not the type (yet);

# what kind of keys do we have:
#
# 1) normal text keys, never contain tabs and are never equal to classless keys
#    two types:
#    - with spaces
#    - without spaces
#
# 2) uniquekeys identify an object as a unique object
#    Syntax:
#    TypeOfObject\tUniqueKeyfield1\t...
#
# 3) keys for inverse lookups
#    
#    TypeOfAttribute\tAttributeValue
#
# 4) Overflow extension file number
#    Syntax:
#    \t (defined in defines.pl by $OVERFLOWKEY)
#  
# 5) Classless keys
#    Syntax:
#    \d+\/\d+
#
# Values are always:
#
# 1) Offset[,Offset [ ... ] ]
# 2) \tNumber
#    Databasefile$OVERFOWEXTENSION$number contains the offsets
# 3) For classless indices:
#    uniquekey of object

require "defines.pl";
require "net2net.pl";
require "dpr.pl";

#
# makekeys
#
# use enkeys() with some tricks to make the searchkeys for whois

#
# we have two cases:
#
# - $tags=""
#   only find normal & classless keys
# - $tags=Valid Attributes separated by spaces
#   only find pointsto keys as specified

sub makekeys {

    local($searchstring, $tags, *others)=@_;
    &dpr("\$searchstring = $searchstring, \$tags = $tags, ",
	 "\@others = ", join(" ", @others), "\n");

    # we make a dummy object for finding the keys
       
    local(%dummyentry)=();
    local(%OLDKEYS)=();
    local(@tmpkeytags)=($tags);
    local(@keys,@other,@pointsto,@otherpointsto,@classless, @tmpkeytags);
    local($OLDKEYS,$OLDPOINTSTO,$classless);
    
    if ($tags!~ /^\s*$/) {
       
       @tmpkeytags=split(/ /, $tags);
       
       @dummyentry{@tmpkeytags}=($searchstring) x @tmpkeytags;
       
       @OLDKEYS{@tmpkeytags}=@KEYS{@tmpkeytags};
       @KEYS{@tmpkeytags}=0;
       
       $classless=0;
    }
    else {
       
       # white space, but no real spaces...
       
       $tags="\t";
       
       $dummyentry{$tags}=$searchstring;
       
       $OLDPOINTSTO=$POINTSTO{$tags};
       $OLDKEYS=$KEYS{$tags};
       
       $POINTSTO{$tags}=0;
       $KEYS{$tags}=1;
       
       $classless=1;
    }

#    &dpr("calling enkeys - args:\n\t", )

    &enkeys(*dummyentry, $tags, *keys, *others, *pointsto, *otherpointsto, *classless, $classless);
    
    &dpr("enkeys returned:\n");
    &dpr("\t\@keys = (", join("*", @keys), ")\n") if (@keys);
    &dpr("\t\@others = (", join("*", @others), ")\n") if (@others);
    &dpr("\t\@pointsto = (", join("*", @pointsto), ")\n") if (@pointsto);
    &dpr("\t\@otherpointsto = (", join("*", @otherpointsto), ")\n") if (@otherpointsto);
    &dpr("\t\@classless = (", join("*", @classless), ")\n") if (@classless);
   
    if ($tags!~ /^\s*$/) {
       
       @KEYS{@tmpkeytags}=@OLDKEYS{@tmpkeytags};
       
       @others=@otherpointsto;
       
       return @pointsto;
       
    }
    else {
       
       #print STDERR "-", @keys, "-";
       #print STDERR "-", @others, "-";
       
       $POINTSTO{$tags}=$OLDPOINTSTO;
       $KEYS{$tags}=$OLDKEYS;
       
       push(@keys, @classless) if (@classless);
       
       return @keys;
       
    }  
      
}

sub enkeys {
    local(*entry, $tags, *keys, *other, *pointsto, *otherpointsto, *classless, $classless)=@_;
    
    local(%keys)=();
    local(%other)=();
    local(%classless)=();
    local(%pointsto)=();
    local(%otherpointsto)=();
    
    local(@values);
    local($value,$newvalue,$code,$tag);

    #
    #  we do multi line matching in some pieces of the next section
    #  of code, so we better localize $*
    
    local($*);
    
    #
    # trickey:
    #
    # split returns "" if $tags=="" 
    # split returns the value when it doesn't match
    #
    # that is what we want when enkeys is called by makekeys
    # and doesn't have $tags defined
    
    foreach $tag (split(/ /,$tags)) {

       # print STDERR "value: $entry{$tag} tag: $tag pointsto: ", $POINTSTO{$tag}, "keys: ", $KEYS{$tag}, "\n";

       #
       # remove:
       #
       # - "," "%" since they are used a separator
       # - most funny characters
       # - trailing dots
       # - leading, trailing and double spaces
       # - substitute all "_" by "-" for ease of use when doing lookups
       
       $*=1;

       #
       # remove most unwanted characters
       ($value=$entry{$tag}) =~ tr/A-Za-z0-9\-\_\:\+\=\.\@\/ \n//cd;

       # print STDERR "enkeys - before line: $value\n";
       
       #
       # remove trailing dots and double spaces
       $value=~ s/\.* +/ /g;
       $value=~ s/\.*$//;
       #
       # only lower case keys, no confusion between _ & -
       $value=~ tr/A-Z_/a-z-/;

#       &dpr("\$tag = $tag, \$value = $value\n");
       
       $*=0;
       
       # print STDERR "enkeys - after line: $value\n";
	
       foreach $value (split(/\n+/, $value)) {
	  
	  next if ($value=~ /^ *$/);

#	  &dpr("\$value = $value, \$classless = $classless\n");
	   
          if (($classless) && ($value=~ /(^| )[\d\:]/)) {
	     
	     # print STDERR "enkeys - line: -$value-\n";

	     # this one is not needed but really speeds up the indexing
	     # normalizerange can handle it but is much slower...
	        
	     if ($value=~ /^(\d+\.\d+\.\d+\.\d+) *\/ *0*([12]?[\d]|3[012])$/) {
                   
                $code=$2;
                
                # print STDERR "enkeys - prefix notation: -$value-$newvalue-$code-\n";
                   
	        next if ((($newvalue=&quad2int($1,0))>=0) &&
                         (!($newvalue & (~$masks[$code]))) &&
                         ($classless{$newvalue."\/".$code}=1));
	        
	     }
	     
	     #
	     # ipv6
	     
	     if ($value=~ /\:/) {
	     
	        if ((&isipv6prefix(($newvalue=&fullipv6formatprefix($value)))) ||
	            (&isipv6prefix(($newvalue=&fullipv6formatprefix($value."\/128"))))) {
	                              
	           $classless{$newvalue}=1;
	              
	           # print STDERR "ipv6 found\n";
	              
	         next;
	        
	        }
	          
	     }
	     
             #
             # special case:
             # 
             # first strip the mask for 'ifaddr:' attributes
                   
             $value=~ s/ +\S+$// if ($tag eq "if");
             
             ($newvalue, $code)=&normalizerange($value, $tag);
#	     &dpr("after normalizerange: \$value = $value, ",
#		  "\$tag = $tag, \$newvalue = $newvalue, \$code = $code\n");
	      
             if ($code==$O_OK) {
                
                @classless{&range2prefixes($newvalue)}=();
#                &dpr("\@classless = (", join(" ", @classless), "\n");
                next;
                
	     }
	         
          }	   
	  
	  @values=grep(length($_)>$SMALLESTKEY, split(/ /, $value));

#	  &dpr("\@values = (", join(" ", @values), ")\n");

          if ($POINTSTO{$tag}) {
	     
	     $tagconnect=$tag."\t";
	     
	     # next one is a special for as macros and as'es
	      
             if (grep($_ !~ /^a[nm]$/, split(/ /, $POINTSTO{$tag}))) {
                
                @pointsto{grep($_=$tagconnect.$_, @values)}=() if (@values);
                $otherpointsto{$tagconnect.$value}=undef if (($value=~ / /) && ($POINTSTO{$tag}=~ /(^| )(pn|ro)( |$)/));
             
             }
             else {
             
                @pointsto{grep(s/^as/${tagconnect}as/, @values)}=() if (@values);
             
                next;
                
             }
             
          }
	        
	  if ($KEYS{$tag}) {
	     # print STDERR "value: $values\n";
             @keys{@values}=() if (@values);
	     $other{$value}=undef if (($value=~ / /) && ($tag=~ /^pn|ro|\t$/));
	  }
	     
       }
	
    }
    
    # print STDERR $keys{$value};
    
    @keys=keys %keys;
    @other=keys %other;
    @pointsto=keys %pointsto;
    @otherpointsto=keys %otherpointsto;
    @classless=keys %classless; 
    
    #print STDERR "enkeys - keys:\n", join(",",@keys), "\n\n";
    #print STDERR "enkeys - other:\n", join(",",@other), "\n\n";
    #print STDERR "enkeys - pointsto:\n", join(",",@pointsto), "\n\n";
    #print STDERR "enkeys - otherpointsto:\n", join(",",@otherpointsto), "\n\n";
    #print STDERR "enkeys - classless:\n", join(",",@classless), "\n\n";

}

1;
