<?php
/*
 * This code is part of GOsa (http://www.gosa-project.org)
 * Copyright (C) 2003-2008 GONICUS GmbH
 *
 * ID: $$Id: class_sudo.inc 9975 2008-03-25 14:09:30Z hickert $$
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


/*! \brief  Sudo option class.
            Allows setting flags/options for a sudo role.
 */
class sudoOption extends plugin
{
  /* Group attributes */
  protected $sudoOption = array();
  public $attributes    = array("sudoOption");
  private $options = array();
  public $ignore_account = TRUE;

  /*! \brief  Initializes this class
        @param  Object $config  The GOsa configuration object.
        @param  String $dn      The object dn.
   */
  function sudoOption(&$config, $dn= NULL)
  {
    plugin::plugin ($config, $dn);

    /****
      Create a list of known options
     ****/
    $options = array();
    $option['long_otp_prompt']= array('NAME' =>'long_otp_prompt' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['ignore_dot']=  array('NAME' =>'ignore_dot' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['mail_always']= array('NAME' =>'mail_always' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['mail_badpass']=  array('NAME' =>'mail_badpass' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['mail_no_user']=  array('NAME' =>'mail_no_user' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['mail_no_host']=  array('NAME' =>'mail_no_host' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['mail_no_perms']= array('NAME' =>'mail_no_perms' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['tty_tickets']= array('NAME' =>'tty_tickets' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['authenticate']=  array('NAME' =>'authenticate' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['root_sudo']= array('NAME' =>'root_sudo' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['log_host']=  array('NAME' =>'log_host' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['log_year']=  array('NAME' =>'log_year' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['shell_noargs']=  array('NAME' =>'shell_noargs' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['set_home']=  array('NAME' =>'set_home' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['always_set_home']= array('NAME' =>'always_set_home' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['path_info']= array('NAME' =>'path_info' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['preserve_groups']= array('NAME' =>'preserve_groups' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['fqdn']=  array('NAME' =>'fqdn' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['insults']= array('NAME' =>'insults' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['requiretty']=  array('NAME' =>'requiretty' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['env_editor']=  array('NAME' =>'env_editor' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['rootpw']=  array('NAME' =>'rootpw' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['runaspw']= array('NAME' =>'runaspw' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['targetpw']=  array('NAME' =>'targetpw' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['set_logname']= array('NAME' =>'set_logname' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['stay_setuid']= array('NAME' =>'stay_setuid' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['env_reset']= array('NAME' =>'env_reset' ,   'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'TRUE');
    $option['use_loginclass']=  array('NAME' =>'use_loginclass' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['noexec']=  array('NAME' =>'noexec' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['ignore_local_sudoers']=  array('NAME' =>'ignore_local_sudoers' ,  'TYPE' => 'BOOLEAN' ,   'DEFAULT' => 'FALSE');
    $option['passwd_tries']=  array('NAME' =>'passwd_tries' ,  'TYPE' => 'INTEGER' ,   'DEFAULT' => 3);
    $option['loglinelen']=  array('NAME' =>'loglinelen' ,  'TYPE' => 'BOOL_INTEGER' ,  'DEFAULT' => 80);
    $option['timestamp_timeout']= array('NAME' =>'timestamp_timeout' ,   'TYPE' => 'BOOL_INTEGER' ,  'DEFAULT' => 0);
    $option['passwd_timeout']=  array('NAME' =>'passwd_timeout' ,  'TYPE' => 'BOOL_INTEGER' ,  'DEFAULT' => 15);
    $option['umask']= array('NAME' =>'umask' ,   'TYPE' => 'BOOL_INTEGER' ,  'DEFAULT' => "0022");
    $option['mailsub']= array('NAME' =>'mailsub' ,   'TYPE' => 'STRING' ,   'DEFAULT' => '*** SECURITY information for %h ***');
    $option['badpass_message']= array('NAME' =>'badpass_message' ,   'TYPE' => 'STRING' ,   'DEFAULT' => 'Sorry, try again');
    $option['timestampdir']=  array('NAME' =>'timestampdir' ,  'TYPE' => 'STRING' ,   'DEFAULT' => '/var/run/sudo');
    $option['timestampowner']=  array('NAME' =>'timestampowner' ,  'TYPE' => 'STRING' ,   'DEFAULT' => 'root');
    $option['passprompt']=  array('NAME' =>'passprompt' ,  'TYPE' => 'STRING' ,   'DEFAULT' => '[sudo] password for %p: ');
    $option['runas_default']= array('NAME' =>'runas_default' ,   'TYPE' => 'STRING' ,   'DEFAULT' => 'root');
    $option['syslog_goodpri']=  array('NAME' =>'syslog_goodpri' ,  'TYPE' => 'STRING' ,   'DEFAULT' => 'notice');
    $option['syslog_badpri']= array('NAME' =>'syslog_badpri' ,   'TYPE' => 'STRING' ,   'DEFAULT' => 'alert');
    $option['editor']=  array('NAME' =>'editor' ,  'TYPE' => 'STRING' ,   'DEFAULT' => '/usr/bin/vi');
    $option['noexec_file']= array('NAME' =>'noexec_file' ,   'TYPE' => 'STRING' ,   'DEFAULT' => '/usr/lib/sudo/sudo_noexec.so');
    $option['lecture']= array('NAME' =>'lecture' ,   'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => 'once');
    $option['lecture_file']=  array('NAME' =>'lecture_file' ,  'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => '');
    $option['logfile']= array('NAME' =>'logfile' ,   'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => 'syslog');
    $option['syslog']=  array('NAME' =>'syslog' ,  'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => 'authpriv');
    $option['mailerpath']=  array('NAME' =>'mailerpath' ,  'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => '');
    $option['mailerflags']= array('NAME' =>'mailerflags' ,   'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => '-t');
    $option['mailto']=  array('NAME' =>'mailto' ,  'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => 'root');
    $option['exempt_group']=  array('NAME' =>'exempt_group' ,  'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => 'root');
    $option['verifypw']=  array('NAME' =>'verifypw' ,  'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => 'all');
    $option['listpw']=  array('NAME' =>'listpw' ,  'TYPE' => 'STRING_BOOL' ,   'DEFAULT' => 'any');
    $option['env_check']= array('NAME' =>'env_check' ,   'TYPE' => 'LISTS' ,   'DEFAULT' => '');
    $option['env_delete']=  array('NAME' =>'env_delete' ,  'TYPE' => 'LISTS' ,   'DEFAULT' => '');
    $option['env_keep']=  array('NAME' =>'env_keep' ,  'TYPE' => 'LISTS' ,   'DEFAULT' => '');
    ksort($option);
    $this->options = $option;

    $this->load_options();
  }


  private function load_options()
  {

    /****
      Parse given sudoOption attributes 
     ****/
    $this->sudoOption = array();
    if(isset($this->attrs['sudoOption'])){
      for($i = 0 ; $i < $this->attrs['sudoOption']['count']; $i++){

        /****
          Detect attribute name/value/negation
         ****/
        $opt = $this->attrs['sudoOption'][$i];

        /* Get negation */ 
        $negation = FALSE;
        if(preg_match("/^!/",$opt)){
          $negation = TRUE; 
          $opt = preg_replace("/^!/","",$opt);
        }

        /* Get value / name*/
        $value    = "";
        if(preg_match("/=/",$opt)){
          $value  = preg_replace("/^[^=]*+=/","",$opt);
          $opt    = preg_replace("/=.*$/","",$opt);
        }

        /* Special chars are escaped, remove escape char now.
            \\ => \
            \: => :
            \, => ,
            \= => = 
         */
        $value = $this->unescape_command($value);

        /* Check if the given value is part of our options list.
           If it is not, add it as type STRING and display a warning.  
        */
        if(!isset($this->options[$opt])){
          $this->options[$opt]=array('NAME'=>$opt,'TYPE'=>'STRING','DEFAULT' => '');
          msg_dialog::display(_("Unknown option"),
              sprintf(_("The sudo option '%s' is invalid!"),
                $opt),WARNING_DIALOG);
        }

        /* Create internal sudoOption object */
        $option = array();
        $option['NAME']   = $opt;
        $option['VALUE']  = $value;
        $option['NEGATE'] = $negation;

        /*  Special handling for mixed flag types. 
            Some attributes like (BOOL_INTEGER) can be TRUE/FALSE and INTEGER.
            This means, if the value is empty it is BOOL and $negation defines its boolean value.
         */
        if(in_array($this->options[$opt]['TYPE'],array("BOOL_INTEGER","STRING_BOOL"))){
          if(empty($value)){
            $option['NEGATE'] = FALSE;
            if($negation){
              $option['VALUE'] = "FALSE";
            }else{
              $option['VALUE'] = "TRUE";
            }
          }
        }

        /* Special handling for BOOLEAN values */
        if(in_array($this->options[$opt]['TYPE'],array("BOOLEAN"))){
          $option['NEGATE'] = FALSE;
          if($negation){
            $option['VALUE'] = "FALSE";
          }else{
            $option['VALUE'] = "TRUE";
          }
        }

        /* Append values */
        $this->sudoOption[$opt][] = $option;
      }
    }
  }



  /*! \brief  Create HTML output for this plugin 
      @return String  HTML output for this plugin.
    */
  function execute()
  {
    /* Call parent execute */
    plugin::execute();

    /*****
      Handle Posts 
     *****/
    if($this->acl_is_writeable("")){

      foreach($_POST as $name => $value){

        if(preg_match("/^negOption_/",$name)){

          $opt = preg_replace("/^negOption_/","",$name);
          $opt = preg_replace("/_[^_]*$/","",$opt);
          $id  = preg_replace("/^.*_([0-9])*$/","\\1",$opt);
          $opt = preg_replace("/_[0-9]*$/","",$opt);

          if(isset($this->sudoOption[$opt][$id])){
            $val = $this->sudoOption[$opt][$id]["VALUE"];

            /*****
              Negate STRING_BOOL && BOOL_INTEGER
             *****/
            if(in_array($this->options[$opt]['TYPE'],array('STRING_BOOL','BOOL_INTEGER'))){
              if(in_array($val, array("TRUE","FALSE"))){
                if($val == "TRUE"){
                  $this->sudoOption[$opt][$id]["VALUE"] = "FALSE";
                }else{
                  $this->sudoOption[$opt][$id]["VALUE"] = "TRUE";
                }
              }else{
                $this->sudoOption[$opt][$id]['NEGATE'] = !$this->sudoOption[$opt][$id]['NEGATE']; 
              }
            }

            /*****
              Negate STRING / INTEGER
             *****/
            if(in_array($this->options[$opt]['TYPE'],array('STRING','INTEGER','LISTS'))){
              $this->sudoOption[$opt][$id]['NEGATE'] = !$this->sudoOption[$opt][$id]['NEGATE']; 
            }

            /*****
              Negate BOOLEAN
             *****/
            if(in_array($this->options[$opt]['TYPE'],array('BOOLEAN'))){
              if($val == "TRUE"){
                $this->sudoOption[$opt][$id]["VALUE"] = "FALSE";
              }else{
                $this->sudoOption[$opt][$id]["VALUE"] = "TRUE";
              }
            }
          }
          break;
        }

        /*****
          Remove options
         *****/
        if(preg_match("/^delOption/",$name)){
          $opt = preg_replace("/^delOption_/","",$name);
          $opt = preg_replace("/_[^_]*$/","",$opt);
          $id  = preg_replace("/^.*_([0-9])*$/","\\1",$opt);
          $opt = preg_replace("/_[0-9]*$/","",$opt);

          if(isset($this->sudoOption[$opt][$id])){
            unset($this->sudoOption[$opt][$id]);
          }
          if(!count($this->sudoOption[$opt])){
            unset($this->sudoOption[$opt]);
          }
          break;
        }
      }
    }

 
    $smarty = get_smarty();
    $smarty->assign("ACL",$this->getacl(""));
    $smarty->assign("map",  array("STRING" => _("string"), "BOOLEAN" => _("bool"),
      "INTEGER" => _("integer") , "BOOL_INTEGER" => _("integer")."-"._("bool") ,
      "STRING_BOOL" => _("string")."-"._("bool"),"LISTS" => _("list")));
    $smarty->assign("sudoOption",$this->prepare_for_html($this->sudoOption));
    $smarty->assign("options",$this->options);
    return($smarty->fetch(get_template_path('options.tpl', TRUE)));
  }


  /*! \brief  Prepare options array to be used in HTML.
      @param  Array   The options array ($this->sudoOption) 
      @return Array   HTML ready sudoOption. Can now be used in smarty templates
   */
  function prepare_for_html($a_options)
  {
    foreach($a_options as $name => $options){
      foreach($options as $key => $option){
        $a_options[$name][$key]['VALUE'] = htmlentities($option['VALUE']);
      }
    }
    return($a_options);
  }


  /*! \brief  Removes this plugin 
   */
  function remove_from_parent()
  {
  }


  /*! \brief  Saves all relevant HTML post values for this plugin 
   */
  function save_object()
  {
    if($this->acl_is_writeable("")){
      plugin::save_object();

      if(isset($_POST['add_option']) && isset($_POST['option'])){
        $opt = get_post("option");

        /* Append attribute only once, lists are handled below */
        if(isset($this->options[$opt])){
          $type = $this->options[$opt]['TYPE'];
          $val  = $this->options[$opt]['DEFAULT'];
          $option = array("NAME" => $opt, "VALUE" => $val , "NEGATE" => FALSE);
          $this->sudoOption[$opt][] = $option;
        }
      }

      foreach($this->sudoOption as $name => $opts){
        foreach($opts as $id => $opt){

          /****
            Get posted value for BOOLEAN
           ****/
          if(in_array($this->options[$name]['TYPE'],array("BOOLEAN"))){
            if(isset($_POST['option_value__'.$name.'_'.$id])){
              $this->sudoOption[$name][$id]["VALUE"] = get_post('option_value__'.$name.'_'.$id);
            }
          }

          /****
            Get posted value for STRING / INTEGER
           ****/
          if(in_array($this->options[$name]['TYPE'],array("STRING","INTEGER"))){
            if(isset($_POST['option_value__'.$name.'_'.$id])){
              $this->sudoOption[$name][$id]["VALUE"] = get_post('option_value__'.$name.'_'.$id);
            }
          }

          /****
            Get posted value for STRING_BOOL / BOOL_INTEGER
           ****/
          if(in_array($this->options[$name]['TYPE'],array("BOOL_INTEGER","STRING_BOOL"))){
            if(isset($_POST['option_selection__'.$name.'_'.$id])){
              $sel = get_post('option_selection__'.$name.'_'.$id);
              $val = "";
              if(isset($_POST['option_value__'.$name.'_'.$id])){
                $val = get_post('option_value__'.$name.'_'.$id);
              }

              if($sel == "FALSE" || $sel == "TRUE"){
                $this->sudoOption[$name][$id]['VALUE'] = $sel;
                $this->sudoOption[$name][$id]['NEGATE'] = FALSE;
              }else{
                $this->sudoOption[$name][$id]['VALUE'] = $val;
              }
            }
          }

          /****
            Get posted value for LISTS
           ****/
          if(in_array($this->options[$name]['TYPE'],array("LISTS"))){
            foreach($this->sudoOption[$name] as $entry_key => $entry){
              if(isset($_POST['list_value__'.$name.'_'.$entry_key])){
                $val = get_post('list_value__'.$name.'_'.$entry_key);
                $this->sudoOption[$name][$entry_key]["VALUE"] = $val;
              }
            } 
          }
        }
      }
    }
  }


  /*! \brief  Save changes to ldap 
   */
  function save()
  {
    plugin::save(); 

    $this->attrs['sudoOption'] = array();
    foreach($this->sudoOption as $name => $opts){
      foreach($opts as $id => $opt){

        $type   = $this->options[$name]['TYPE'];
        $neg    = $opt['NEGATE'];
        $value  = $opt['VALUE'];
        $option = "";

        /* Escape special chars */
        $value = $this->escape_command($value);

        /****
          Save LISTS 
         ****/
        if($type=="LISTS"){
          if($value == ""){
            $option = $name;
          }else{
            $option = $name."=".$value;
          }
          if($neg){
            $option = "!".$option;
          }
        }

        /****
          Save BOOLEAN
         ****/
        if(in_array($type,array("BOOLEAN"))){ 
          $option = $name;
          if($value == "FALSE"){
            $option = "!".$option;
          }
        }

        /****
          Save STRING / INTEGER
         ****/
        if(in_array($type,array("STRING","INTEGER"))){ 
          if($value != ""){
            $option = $name."=".$value;
          }else{
            $option = $name; 
          }
          if($neg){
            $option = "!".$option;
          }
        }

        /****
          Save STRING_BOOL / BOOL_INTEGER
         ****/
        if(in_array($type,array("STRING_BOOL","BOOL_INTEGER"))){
          if($value == "FALSE"){
            $option = "!".$name;
          }elseif($value == "TRUE"){
            $option = $name;
          }else{
            if($value != ""){
              $option = $name."=".$value;
            }else{
              $option = $name; 
            }
            if($neg){
              $option = "!".$option;
            }
          }
        }

        $this->attrs['sudoOption'][] = $option;
      }
    }
    $this->cleanup();
    $ldap = $this->config->get_ldap_link();
    $ldap->cd($this->dn);
    $ldap->modify($this->attrs);;
  }


  /*! \brief  Checks input validity
   */
  function check()
  {
    $message = plugin::check();

    foreach($this->sudoOption as $name => $options){
      foreach($options as $id => $option){
        switch($this->options[$name]['TYPE']){

          /* Check for a valid integer value */
          case 'INTEGER' : 
            {
              if(!preg_match("/^[0-9]*$/",$option['VALUE'])){
                $message[] = msgPool::invalid($name,$option['VALUE'],"/[0-9]/");
              }
            } break;
        }
      }
    }
    return ($message);
  }


  /*! \brief  This function will be called if an object gets copied.
              This function adapts attributes from the source object.
      @param  Array The source object.
   */
  function PrepareForCopyPaste($source)
  {
    plugin::PrepareForCopyPaste($source);
    if(isset($source['sudoOption'])){
      $this->attrs['sudoOption'] = $source['sudoOption'];
      $this->load_options();
    }
  }


  /*!  \brief   Escape special chars in function parameters.
       @param   String the string to that must be escaped.
   */
  private function escape_command($str)
  {
    /* Check if given value is a command (/[a-z]/ ..)
     */
    if(preg_match("/^\//",$str)){
      $cmd = preg_replace("/^([^ ]*).*$/","\\1",$str);
      $val = preg_replace("/^[^ ]*(.*)$/","\\1",$str);
      $str = $cmd.addcslashes($val,":.,\\");
    }
    return($str);
  }


  /*! \brief  Add ACL object
      @return Returns the ACL object.
   */
  static function plInfo()
  {
    return (array(  
          "plShortName" => _("Options"),
          "plDescription" => _("Sudo options"),
          "plSelfModify"  => FALSE,
          "plDepends"     => array(),
          "plPriority"    => 2,
          "plSection"     => array("administration"),
          "plCategory"    => array("sudo"),
          "plProvidedAcls"    => array()
        ));
  }

  /*!  \brief   Unescape special chars in function parameters.
       @param   String the string to that must be unescaped.
   */
  private function unescape_command($str)
  {
    /* Check if given value is a command (/[a-z]/ ..)
     */
    if(preg_match("/^\//",$str)){
      $cmd = preg_replace("/^([^ ]*).*$/","\\1",$str);
      $val = preg_replace("/^[^ ]*(.*)$/","\\1",$str);
      $val = preg_replace(array("/\\\\\\\\/","/\\\\,/","/\\\\:/","/\\\\=/"),
                              array("\\",",",":","="),$val);
      $str = $cmd.$val;
    }
    return($str);
  }
}
// vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
?>
