#!/usr/bin/perl

# ------------------------------------------------------------------
#
#    Copyright (C) 2002-2005 Novell/SUSE
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of version 2 of the GNU General Public 
#    License published by the Free Software Foundation.
#
# ------------------------------------------------------------------



use warnings;
use strict;
use Data::Dumper;
use Encode;
use Getopt::Long;
use Locale::gettext;
use POSIX;
use Immunix::AppArmor;

# define textdomain so that y2makepot does not complain ..
textdomain("yast2-apparmor");
# .. and re-define it here. y2makepot does not mind
# and strings from y2-apparmor are translated differently anyway
textdomain("apparmor-utils");

use Immunix::AppArmor;
setup_yast();

# !hack hack hack!
# Can't use YaST _macro for marking strings
# for translation, so we need to do it Perl-way
setlocale(LC_MESSAGES, "");

my $dom = Locale::gettext->domain_raw("yast2-apparmor");
$dom->dir("/usr/share/YaST2/locale");
$dom->codeset("UTF-8");

#bubli's own variation of gettext, because we need to
#translate strings in two textdomains
sub mygettext
{
    my $msgid = shift;
    return $dom->get("$msgid");
}

# __ is an alias for mygettext - this is necessary because
# y2makepot does not know mygettext keyword
*__ = \&mygettext;

$running_under_genprof = 1;

# options variables
my $help           = '';
  
GetOptions(
  'file|f=s'    => \$filename,
  'dir|d=s'     => \$profiledir,
  'help|h'      => \$help,
);
  
# tell 'em how to use it...
&usage && exit if $help;
my $aa_mountpoint = Immunix::AppArmor::check_for_apparmor();
unless($aa_mountpoint) {
  fatal_error( __("AppArmor does not appear to be started.  Please enable AppArmor and try again."));
}

# let's convert it to full path...
$profiledir = get_full_path($profiledir);
  
unless(-d $profiledir) {
  fatal_error(sprintf(__("Can't find apparmor profiles in %s."), $profiledir));
}

# what are we profiling?
my $profiling;
my $fqdbin;

do {

  my $f = {
    description  =>

__("This wizard will assist you in creating a new AppArmor security 
profile for an application, or you can use it to enhance 
an existing profile by allowing AppArmor to learn new 
application behavior. 

Enter the application name for which you would like
to create a profile or select Browse to find the 
application on your system.
") ,
    file_label   => __("&Application to Profile"),
    okay_label   => __("&Create"),
    cancel_label => __("&Abort"),
    browse_desc  => __("Select Program to Profile"),
  };

  my $profiling = UI_GetFile( $f );
  if(not defined $profiling) {

    # they hit cancel
    shutdown_yast();
    exit 0;

  } elsif($profiling) {

    # they selected something, see if it exists or we can find it in $PATH
    $fqdbin = "";
    if(-f $profiling) {
      $fqdbin = get_full_path($profiling);
      chomp($fqdbin);

      unless(-x $fqdbin) {
        UI_Important( __("The specified file is not executable.

Enter an application name to
continue generating a profile or press
Abort to cancel this wizard.
"));
      }
    } elsif(-d $profiling) {
      UI_Important( __("The specified pathname is a directory.

Enter an application name to
continue generating a profile or press
Abort to cancel this wizard.
"));
    } else {
      if($profiling !~ /\//) {
        my $which = which($profiling);
        if($which) {
          $fqdbin = get_full_path($which);
        }
      }

      unless(-f $fqdbin) {
        UI_Important( __("The specified file does not exist.

Enter an application name to
continue generating a profile or press
Abort to cancel this wizard.
"));
      }
    }

  } else {
    # they hit okay without entering anything
    UI_Important( __("You have not entered or selected an
application to profile.

Enter an application name to
continue generating a profile or press
Abort to cancel this wizard.
"));
  }

} until($fqdbin && -x $fqdbin);

# make sure that the app they're requesting to profile is not marked as
# not allowed to have it's own profile
check_qualifiers($fqdbin);

# load all the include files
loadincludes();

my $profilefilename = getprofilefilename($fqdbin);
if(-e $profilefilename) {
  $helpers{$fqdbin}  = getprofileflags($profilefilename) || "enforce";
} else {
  autodep($fqdbin);
  $helpers{$fqdbin} = "enforce";
}

if($helpers{$fqdbin} eq "enforce") {
  complain($fqdbin);
  reload($fqdbin);
}

my $done_profiling = 0;
my $syslog = 1;
$syslog = 0 if ( -e "/var/log/audit/audit.log" );

while(not $done_profiling) {
   
  my $logmark = "";
  if ( $syslog ) {
    $logmark = `date | md5sum`;
    chomp $logmark;
    $logmark = $1 if $logmark =~ /^([0-9a-f]+)/;
    system("logger -p kern.warn 'GenProf: $logmark'");
  } else {
    $logmark = last_audit_entry_time();
  }

  my $q = { };
  $q->{headers}   = [ __("Profiling"), $fqdbin ];
  $q->{explanation} = __("Start the application to be profiled in
another window and exercise its functionality now.

Once completed, select the 'Scan' option below in
order to scan the system logs for AppArmor events.

For each AppArmor event, you will be given the
opportunity to choose whether access should be
allowed or denied.
");
  $q->{functions} = [ "CMD_SCAN", "CMD_FINISHED" ];
  $q->{default}   = "CMD_SCAN";


  eval {
    my ($ans, $arg) = UI_PromptUser($q);
    if($ans eq "CMD_SCAN") {

      my $lp_ret = do_logprof_pass($logmark);

      $done_profiling = 1 if $lp_ret eq "FINISHED";

    } else {

      # make them confirm the exit command
      my $ans = UI_YesNo( __("Are you sure you want to exit?"), "n");
      if($ans eq "y") {
        $done_profiling = 1;
      }

    }
  };
  if ($@) {
    if ($@ =~ /FINISHING/) {
      $done_profiling = 1;
    } else {
      die $@;
    }
  }
}
  
for my $p (sort keys %helpers) {
  if($helpers{$p} eq "enforce") {
    enforce($p);
    reload($p);
  }
}

UI_Info(__("Reloaded AppArmor profiles in enforce mode."));
UI_Info( sprintf(__('Finished generating profile for %s.'), $fqdbin));

shutdown_yast();

exit 0;

sub usage {
  UI_Info("usage: $0 [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to profile ]");
  exit 0;
}

sub last_audit_entry_time {

  local $_ = `tail -1 /var/log/audit/audit.log`;
  my $logmark;
  if ( /^.*msg\=audit\((\d+\.\d+\:\d+).*\).*$/ ) {
    $logmark = $1;
  } else {
    $logmark = "";
  }
  return $logmark;
}

