#
# $Id: cross-notify.pl,v 2.9 1999/10/06 16:41:02 marek Exp $
#
# cross-notify.pl - generate notifications when overlapping route objects
#                   are updated.
#
# 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.

#
# add_cross_notify
#
# generate notifications of route objects with overlapping address
# space.
#
# called when objects have been updated
# only route objects are examined all others are ignored.
#
# foreach route-obj
#    check for overlapping route
#       exact match
#       less specific
#       more specific
#
#    for each overlapping route build notification wrt that route if it
#    or it's aut-num obj has cross-nfy: set.
#
# assumes all route objects are expressible as a single IP prefix
# (unlike inetnum objects which can represent groups of /24s that are not
# on a /23 (or whatever) boundary).
# This means that when comparing routes there are only 3 cases,
#
#    A == B - the two objects represent exactly the same address space
#    A < B  - A space is completely contained within B space
#    A > B  - B space is completely contained within A space
#
#

# Specification of new attributes for RR Cross Notification.

# The new attributes cross-nfy: & cross-mnt: are valid in 'route' and
# 'autnum' objects.  Any route or autnum object may have zero or more
# of either or both of these attributes.

# When any route object is added or deleted a more specific and a less
# specific query is made to find other route objects that represent
# part or all of the address space represented by the updated object
# (called a 'overlap')

# The set of sources searched for these overlaps is defined on a
# server wide basis (CROSSNOTIFYSOURCES in ripedb.conf).

# 'route' objects with the same value for the 'route' (prefix) and
# 'origin' attributes but from a different source will be ignored even
# if they are in a source listed in CROSSNOTIFYSOURCES. This will
# occur when network operators have registered the same 'route' object
# to different routing registries which are running mirrors of each
# other.

# If an overlap is found, the updated route object and the overlapped
# route objects are examined for a 'cross-nfy:' or 'cross-mnt:'
# attribute. If these attributes are found the email addresses they
# (indirectly) refer to are sent a 'Cross Notification' Notice to
# alert the interested parties of the overlap.

# The sender of the update is sent a separate notification of the
# overlap whether or not their address is refered to in an cross-nfy
# or cross-mnt attribute.

# Regardless of whether the 'route' object contains 'cross-nfy:' or
# 'cross-mnt:' attributes, the 'autnum' objects refered to by the
# (mandatory) 'origin' attribute of the route objects are also
# examined for 'cross-nfy:' and 'cross-mnt:' attributes.  If these
# attributes exist they are treated exactly as if they had been
# included in the refering 'route' object.

# The value of a 'cross-nfy:' attribute is a NIC handle refering to
# 'person' or 'role' object. If this object exists the value of any
# 'email:' attributes it contains are added to the list of email
# addresses to notify.

# The value of a 'cross-mnt:' attribute is a 'mntner' name refering to
# to a 'mntner' object. If this object exists the value of any
# 'mnt-nfy:' attributes it contains are added to the list of email
# addresses to notify.

# The database queries made to follow the 'origin', 'cross-nfy:' and
# 'cross-mnt:' references will be done on the 'default' set of sources
# (from DEFLOOK in ripedb.conf)

# Once all of these references have been resolved a (possibly empty)
# list of email addresses will have been produced. Duplicate addresses
# are removed and a notice of the overlap sent to each unique
# address.

# The confict notifications will be tailored to the individual
# recipients so they are only informed about their 'own' objects. It
# will be made clear which overlaps are exact matches, which are
# overlaps and whether the updated object has just been added or
# deleted.

# Note: the following paragraph has NOT been implemented

# Notifications sent to a particular recipient SHOULD include all of
# the recipient's objects that are in overlap with the newly updated
# 'route' object so only one notification is sent to each
# recipient. Notifications SHOULD also include an indication of the
# chain of references followed to arrive at the email address of the
# recipient. This will help in tracing unwanted mail.

# The notification sent to the sender of the update will include a
# list of all of the objects that overlap the updated object and will
# be separate from any other notifications sent to the same address.

# There follows a diagram of the references involved

#  route -> cross-nfy -> person/role -> email
#  route -> cross-mnt -> mntner -> mnt-nfy (email)
#  route -> origin -> autnum -> cross-nfy -> person/role -> email
#  route -> origin -> autnum -> cross-mnt -> mntner -> mnt-nfy (email)

# DEBUGGING

# There are two magic environment variables used for testing
#
#  RIPEDB_CN_RECIP - an email address to send the notifications to
#                    rather than those specified in the objects.
#                    These recipient addresses are included in a
#                    x-ripedb-cn-really-to: header.
#
#  RIPEDB_CN_ORIG  - when the update didn't come my mail (-M) use the
#                    value as the originator address (from:).
#                    Useful for testing when you aren't feeding mail messages
#                    to updatedb.

require "cross-misc.pl";

sub add_cross_notify {
#  use strict;

  # creation of new object leaves $old undef
  # deletion of old object leaves $new undef
  my(
     $old,			# Object being deleted (if any)
     $new,			# Object being added (if any)
     $originator,		# Originator of update message
     ) = @_;         

  # nothing to do if it is just updating an existing object
  #
  if (defined $old && defined $new) {
    return ();
  }

  my($object);
  if (defined $old) {
    $object = $old;
  } else {
    $object = $new;
  }

  # nothing to do if it is not a route (rt) object
  #
  unless (exists $object->{rt}) {
    return ();
  }

  # list of sources to search for overlaps in
  # use value of CROSSNOTIFYSOURCES from config file if it exists
  # otherwise just use the source of the object
  my(@sources) = undef;
  { 
    if (exists $::CROSSNOTIFYSOURCES{$object->{so}}) {
      @sources = @{$::CROSSNOTIFYSOURCES{$object->{so}}};
    } else {
      @sources = $object->{so};
    }
  }
  
  {
    # an array to put the resulting routes in
    my(@all_overlaps);

    # key   - email addresses to notify
    # value - array of routes to notify about
    my(%email_addresses);

    ### FOREACH SOURCE

    my($source);
    SOURCE: foreach $source (@sources) {

      ## get list of overlapping routes
      ## returns ref to array of route objects

      my($routes) = find_overlaps($source, $object);

      ## skip to next source if there are no overlaps

      next SOURCE unless (@{$routes});

      ## build a hash
      ##  key is origin of route
      ##  value is array of routes with that origin

      my($origins) = find_origins($routes);

      ## get all referenced autnum objects from database

      my(@autnums);
      {
	# convert origin fields to lowercase for the lookup
	# AS65534 -> as65534
	my(@keys) = keys (%{$origins});
	@keys = map { lc $_ } @keys;

	@autnums =
	  source_lookup(
			$source,
			'an',
			[@keys],
			{Exactmatch=>1}
			);
      }
      ## add cross-notify attribute of autnum to route objects that
      ## refer to it in their origin

      add_autnum_cross_notifies($origins, \@autnums);

      ## add route's own cross-notify attribs to same field

      add_routes_cross_notifies($routes);

      ## build two hashes
      ##
      ##   cross_nfy - all nic-hdl's referenced in cross-nfy: attribs
      ##   cross_mnt - all mntner's  referenced in cross-mnt: attribs
      ##
      ## value is array of routes which reference the nic-hdl/mntner

      my($cross_nfy, $cross_mnt) = build_hash_of_referenced_objs($routes);

      ## lookup all mntner, person and role objects that have been
      ## referenced

      my(@referenced_nfy_objects) = undef;
      {
	my(@keys) = keys (%{$cross_nfy});
	# convert keys to lowercase for the lookup
	@keys = map { lc $_ } @keys;
	@referenced_nfy_objects =
	  source_lookup(
			$source,
			[ qw/pn ro/ ],
			[ @keys ],
			{ Exactmatch=>1 },
			);
      }

      my(@referenced_mnt_objects) = undef;
      {
	my(@keys) = keys (%{$cross_mnt});
	# convert keys to lowercase for the lookup
	@keys = map { lc $_ } @keys;
	@referenced_mnt_objects =
	  source_lookup(
			$source,
			'mt',	# 
			[ @keys ],
			{ Exactmatch=>1 },
			);
      }

      ## build indexes of these two arrays

      ## persons & roles in cross_nfy_objs
      my(%cross_nfy_objs);
      {
	my ($obj);
	foreach $obj (@referenced_nfy_objects) {
	  my($key);

	  # nh - 'nic-hdl'
	  next unless (exists $obj->{nh});

	  $key = $obj->{nh};

	  # em - 'email'
	  if (exists $obj->{em}) {
	    my(@email);

	    @email = split("\n", $obj->{em});
	    $obj->{Extra}->{Email} = [ @email ];
	  }

	  $cross_nfy_objs{$key} = $obj;
	}
      }

      ## mntners in cross_mnt_objs
      my(%cross_mnt_objs);
      {
	my ($obj);
	foreach $obj (@referenced_mnt_objects) {
	  my($key);

	  # mt - 'mntner'
	  next unless (exists $obj->{mt});
	  $key = $obj->{mt};

	  # dt - 'upd-to'
	  if (exists $obj->{dt}) {
	    my (@email);

	    @email = split("\n", $obj->{dt}) ;
	    $obj->{Extra}->{Email} = [ @email ];
	  }

	  $cross_mnt_objs{$key} = $obj;
	}
      }


      ## foreach route follow the cross-nfy & cross-mnt references
      ## and build hash of email addresses with the routes to tell 
      ## them about


      my($route);
      foreach $route (@{$routes}) {

	# cross-nfy:
	{
	  my($nfy, $count);
	  while (($nfy, $count) = each %{$route->{Extra}->{Cross_nfy}}) {
	    my ($email);
	    foreach $email (@{$cross_nfy_objs{$nfy}->{Extra}->{Email}}) {
	      push @{$email_addresses{$email}}, $route;
	    }
	  }
	}

	# cross-mnt:
	{
	  my($mnt, $count);
	  while (($mnt, $count) = each %{$route->{Extra}->{Cross_mnt}}) {
	    my($email);
	    foreach $email (@{$cross_mnt_objs{$mnt}->{Extra}->{Email}}) {
	      push @{$email_addresses{$email}}, $route;
	    }
	  }
	}
      }

      push @all_overlaps, @{$routes};

    } ## foreach source


    ## return empty-handed if there are no overlaps

    return () unless @all_overlaps;

  
    # an array of filenames that contain mail messages to be sent
    undef my @mailfiles;

    ### DEBUG

    # if no originator is specified (not using -M) and our special env
    # variable exists use its value as an originator email address

    if (!$originator and exists $ENV{RIPEDB_CN_ORIG}) {
      $originator = $ENV{RIPEDB_CN_ORIG} 
    }

    if ($originator) {
      push @mailfiles,
      create_mail_to_updater($originator, $object, \@all_overlaps, $new);

      # If the originator address is also on the list of notifiees
      # remove it so they don't get two messages
      # this message includes all the information.
      delete $email_addresses{$originator};
    }

    push @mailfiles,
    create_mail_to_others(\%email_addresses, $object, $new);

    return @mailfiles;
  }
}

# send the previously built mail messages

sub send_cross_notifications {
  my(@mailfiles) = @_;

  my ($notice);
  foreach $notice ( sort @mailfiles ) {
    system("$MAILCMD < $notice");
    print STDERR "send_cross_notifications: $notice\n" if ($opt_V);

# DEBUGGING
#    print "[$notice]\n";
#    system "/bin/cat $notice";

    unlink($notice);
  }

}

1;
