#!/usr/bin/php -q
<?php
//
//  Note: /usr/bin/php should be compiled with --enable-pcntl
//  This is the main module, it contains:
//  - commandline parameter parsing
//  - backgrounding 
//  - and the main loop.
//

// Global config stuff.
error_reporting (E_ALL & ~E_NOTICE);

set_time_limit (0);
ob_implicit_flush();

// important global objects
// included here for reference only
global $debug;
global $modules;       // array of references to functional objects
global $db;            // database object
global $tmpdir;        // temp dir for config files
global $astglobals;    // all values from the astglobals table
global $astglobalname; // astglobal name by id
global $astglobalcat;  // astglobal category by id

$options = getopt("hd:m:");

if (isset($options['h'])) {
  printf("usage: astcfgd [-h] [-d n]\n");
  exit(0);
}
$debug = $options["d"];
$mode  = $options["m"];

include "/usr/local/astium/web/php/config.php";
include "/usr/local/astium/web/php/cms/util.php";
include "/usr/local/astium/web/php/log.php";

require_once('Net/IPv4.php');

include "/usr/local/astium/modules/astium-functions.php";

$LOGFILE = "/usr/local/astium/web/html/log/error.log"; // so that log_msg() knows where to write to.

class module {
  function idle() {
    return("OK");
  }
  // action = add|delete|modify|config
  // if action = add|delete|modify 
  //   type   = department|contact|phone
  //   which  = numeric
  // if action = module
  //   type   = asterisk|all
  //   which  = all
  function action($action, $type, $which) {
    return("OK");
  }
  // create files temporary directory
  function config() {
    return("OK");
  }
  // copy created files to production directory
  // (for imap create user here)
  function activate() {
    return("OK");
  }
}

/*
   PHP forked daemon
   Standalone PHP binary must be compiled with --enable-sockets and --enable-pcntl
*/
function sig_handler($signo) {

     global $running;
     switch($signo) {
         case SIGTERM:
             // handle shutdown tasks
             //$running = false;
             exit;
             break;
         case SIGHUP:
             // handle restart tasks
             break;
         case SIGUSR1:
              log_msg(__FILE__, __LINE__, "Caught SIGUSR1...");
             break;
         case SIGCHLD:
             while( pcntl_waitpid(-1,$status,WNOHANG)>0 ) {
             }
             break;
         case SIGINT:
       exit;
         default:
             // not implemented yet...
             break;
     }

}


function become_daemon() {

   $child = pcntl_fork();
   if($child) {
       exit; // kill parent
   }
   posix_setsid(); // become session leader
   chdir("/");
   umask(0); // clear umask
   return posix_getpid();

}

function open_pid_file($file) {

   if(file_exists($file)) {
       $fp = fopen($file,"r");
       $pid = fgets($fp,1024);
       fclose($fp);
       if(posix_kill($pid,0)) {
           print "Server already running with PID: $pid\n";
           exit;
       }
       print "Removing PID file for defunct server process $pid\n";
       if(!unlink($file)) {
           print "Cannot unlink PID file $file\n";
           exit;
       }
   }
   if($fp = fopen($file,"w")) {
       return $fp;
   } else {
       print "Unable to open PID file $file for writing...\n";
       exit;
   }
}

function change_identity($uid,$gid) {
   global $pid_file;
   if(!posix_setgid($gid)) {
       print "Unable to setgid to $gid!\n";
       unlink($pid_file);
       exit;
   }   
   if(!posix_setuid($uid)) {
       print "Unable to setuid to $uid!\n";
       unlink($pid_file);
       exit;
   }
}

// Create filename temp directory.
$tmpdir = strftime("/tmp/astcfg-%y%m%d-%H%M%S");

include "/usr/local/astium/modules/asterisk.php";
include "/usr/local/astium/modules/dhcp.php";
include "/usr/local/astium/modules/upwatch.php";
include "/usr/local/astium/modules/firewall.php";
include "/usr/local/astium/modules/network.php";
include "/usr/local/astium/modules/ipsec.php";
include "/usr/local/astium/modules/pptp.php";
include "/usr/local/astium/modules/ntpd.php";
include "/usr/local/astium/modules/tftp.php";
include "/usr/local/astium/modules/zaptel.php";
include "/usr/local/astium/modules/amavisd.php";
include "/usr/local/astium/modules/postgresql.php";
include "/usr/local/astium/modules/services.php";
include "/usr/local/astium/modules/fax.php";

// Install signal handlers.
//pcntl_signal(SIGCHLD, "sig_handler");
//pcntl_signal(SIGTERM, "sig_handler");
//pcntl_signal(SIGINT,  "sig_handler");
pcntl_signal(SIGHUP,  "sig_handler");

if ($debug == 0) {

  // Write 
  $pid_file = '/var/run/astcfgd.pid';
  $fh = open_pid_file($pid_file);

  // Become a daemon process.
  $pid = become_daemon();
  fputs($fh,$pid);
  fclose($fh);
  define_syslog_variables();
  ini_set('error_log', 'syslog');
}

// Create database connections.
$act_here  = new DB_CMS;
$db        = new DB_CMS;
$local     = new DB_CMS;
$service   = new services;

$db->query("select * from astglobals where name = 'synchronize_on'");
if ($db->num_rows() > 0) {
  $db->next_record();
  if ($db->f('value') == 'yes') $mode = 'slave';
}

if ($mode == 'slave') {
  $db->query("select * from astglobals where name = 'synchronize_ip'");
  if ($db->num_rows() > 0) {
    $db->next_record();
    $synchronize_ip = $db->f('value');
  } else {
    $mode = '';
    break;
  }

  $act_there = new DB_Remote;
  $remote    = new DB_Remote;

  $act_there->Host = $synchronize_ip;
  $remote->Host    = $synchronize_ip;

  // Make sure astcfgd doesn't stop when connection to remote database fails.
  $act_there->NoHalt = 1;
  $remote->NoHalt    = 1;
}

if ($debug > 2) $act_here->Debug = 1;
if ($debug > 2) $db->Debug       = 1;
if ($debug > 2) $local->Debug    = 1;
if ($debug > 2) $remote->Debug   = 1;

// Remove records in activate table where type=system-restart and status=QUEUED.
// This is done because when the system is restarted, the status of the activate record which initated it hasn't changed.

$db->query("delete from activate where (type='system-restart' or type='system-shutdown') and status='QUEUED'");

// 
// main loop
// 
// - for each record in the activation table:
// - check if basic data is present
// - call each module, let the module decide which files to create/change, 
//   or what to configure
// - (if something fails - set activation record to ERROR, and restart the loop)
// - for each module: create the module config function
// - for each module: activate configuration
// - sleep 1;
// - for each module: call idle function
//

$running = true;
while ($running) {

  unset($astglobals);
  unset($astglobalname);
  unset($astglobalcat);
  $db->query("select * from astglobals");
  while ($db->next_record()) {
    $astglobals[$db->f('name')]  = $db->f('value');
    $astglobalname[$db->f('id')] = $db->f('name');
    $astglobalcat[$db->f('id')]  = $db->f('category');
  }

  // boolean variable set to true when 1 or more activate records were found.
  $config = false;

  // First process local activate records.

  $act_here->query("select * from activate where (status='QUEUED' or status='REQUEUED') and process < now()");
  if ($act_here->num_rows() > 0) {
    $config = true;
    while ($act_here->next_record()) {
      $id     = $act_here->f('id');
      $action = $act_here->f('action');
      $type   = $act_here->f('type');
      $which  = $act_here->f('which');

      if ($debug > 2) echo "[$id / $action / $type / $which]\n";

      if ($action == '' || $type == '' || $which == '') {
        $db->query("rollback");
        $act_here->query("update activate " .
                         "set    status='ERROR', processed = NOW(), message = 'malformed activate record' " .
                         "where  id = '$id'");
        break;
      }

      // precheck if this record exists, so the modules don't have to
      $table = $type;  // preset
      switch ($type) { // handle exceptions
      case 'user';              $table = "contact" ;   break;
      default:                  $table = "";
      }

      if ($table != "") {
        $db->query("select id from $table where id = $which");
        if ($db->num_rows() != 1) {
          $act_here->query("update activate " .
                        "set    status='ERROR', processed = NOW(), message = 'record $which in table $table not found' " .
                        "where  id = '$id'");
          break;
        }
      }

      // give this activation to all modules, so they can check if
      // everything necessary for activation is present.
      $db->query("begin");
      if ($debug > 0) printf("STAGE ACTION: action=$action, type=$type, which=$which\n");
      foreach ($modules as $key => $module) {
        if ($debug > 0) printf("%s ", $key);
        $rc = $modules[$key]->action($action, $type, $which);
        if ($rc != "OK") { // some problem happened - this activation record must be disabled
          if ($debug > 0) printf("unexpected error during action $key: $rc\n");
          $db->query("rollback");
          $act_here->query("update activate " .
                        "set    status='ERROR', processed = NOW(), message = '$rc' " .
                        "where  id = '$id'");
          break; // the foreach loop
        }
      }
      $db->query("commit");
      if ($debug > 0) printf("\n");

      if ($rc != "OK") {
        break;
      }

    } // while $act_here->next_record();
  } // $act_here->num_rows() > 0

  if ($mode == 'slave') {
    $local->query("select * from astglobals where name='activated'");
    if ($local->num_rows() == 0) {
      $db->query("select max(id) as latest from activate");
      $db->next_record();
      $activated = $db->f('latest');
      $q = sprintf("insert into astglobals (name, value, category) values ('activated', '%s', 'system')", $activated);
      $local->query($q);
    } else {
      $local->next_record();
      $activated = $local->f('value');
    }
  
    $act_there->query("select * from activate where id > $activated order by id asc");
    unset($id);
    while ($act_there->next_record()) {
      $config = true;

      if (isset($id)) { // $id comes from previous loop, if this isn't the first one.
        $q = sprintf("update astglobals set value='%s' where name='activated'", $id);
        $local->query($q);
      }

      $id     = $act_there->f('id');
      $action = $act_there->f('action');
      $type   = $act_there->f('type');
      $which  = $act_there->f('which');

      if ($action == '' || $type == '' || $which == '') {
        continue;
      }

      // Instead of checking if the record exists, get the record from the master

      $table = $type;  // preset
      switch ($type) { // handle exceptions
        case 'user': $table = "contact"; break;
      }

      // exceptions
      $exceptions = array("activate", "astium_cdr", "billnotes", "callhist", "trunk", "astglobals", "callrouting", 
                          "dmz_to_external", "dmz_to_internal", "external_to_dmz", "external_to_internal", "internal_to_dmz", "internal_to_external",
                          "ipsec_hosts", "to_intf_external", "zap_channel");
      if (in_array($type, $exceptions)) continue;
      if (substr($type, 0, 4) == 'card') continue;
      if (substr($type, 0, 8) == 'service-') continue;

      if ($action != 'module') {
        $res = $remote->metadata($table);
        $fields = array();
        foreach($res as $field) {
          $fields[] = $field['name'];
        }
        if ($table == 'pptpusers') {
          $identifier = 'username';
          $remote->query("select username from contact where id='$which'");
          $remote->next_record();
          $orig_which = $which;
          $which = $remote->f('username');
        } else {
          $identifier = 'id';
        }

        $remote->query("select * from $table where $identifier = '$which'");

        if ($remote->num_rows() == 1) {
          $remote->next_record();
          if ($action == 'modify') {
            $q = "update $table set ";
            foreach($fields as $field) {
              if ($remote->isnull($field)) {
                $q .= sprintf("%s = NULL, ", $field);
              } else {
                $q .= sprintf("%s = '%s', ", $field, $remote->f($field));
              }
            }
            $q = substr($q, 0, -2);
            $q .= sprintf(" where %s='%s'", $identifier, $which);
            $local->query($q);
          }
          if ($action == 'add') {
            $q = "insert into $table (";
            foreach ($fields as $field) {
              $q .= sprintf("%s, ", $field);
            }
            $q = substr($q, 0, -2);
            $q .= ") values (";
            foreach ($fields as $field) {
              if ($remote->isnull($field)) {
                $q .= sprintf("NULL, ");
              } else {
                $q .= sprintf("'%s', ", $remote->f($field));
              }
            }
            $q = substr($q, 0, -2);
            $q .= ")";
            $local->query($q);
          }
          if ($action == 'delete') {
            $q = sprintf("update %s set status = 'deleted' where %s = '%s'", $table, $identifier, $which);
            $local->query($q);
          }
        }
      }

      if ($table == 'pptpusers') $which = $orig_which;

      // now write an activate record to the local activate table, so it will act upon it.
      $query = sprintf("insert into activate(action, type, status, created, which, process) values " .
                       "('%s', '%s', '%s', '%s', '%s', '%s')", 
                       $act_there->f('action'), $act_there->f('type'), $act_there->f('status'),
                       $act_there->f('created'), $act_there->f('which'), $act_there->f('process'));
      $local->query($query);
    } // while $act_there->next_record();

    if (isset($id)) { // $id comes from previous loop, if this isn't the first one.
      $q = sprintf("update astglobals set value='%s' where name='activated'", $id);
      $local->query($q);
    }
  } // if ($mode == 'slave')

  if ($config) {
    $db->query("begin");

    if ($debug > 0) printf("STAGE CONFIG:\n");
    foreach ($modules as $key => $module) {
      if ($debug > 0) printf("%s ", $key);
      $rc = $modules[$key]->config();
      if ($rc != "OK") {
        if ($debug > 0) printf("unexpected error during config: $rc\n");
        break;
      }
    }
    if ($debug > 0) printf("\n");

    if ($debug > 0) printf("STAGE ACTIVATE:\n");
    foreach ($modules as $key => $module) {
      if ($debug > 0) printf("%s ", $key);
      $rc = $modules[$key]->activate();
      if ($rc != "OK") {
        if ($debug > 0) printf("unexpected error during config: $rc\n");
        break;
      }
    }
    if ($debug > 0) printf("\n");

    $act_here->query("update activate " .
                     "set    status = 'DONE', message = 'OK', processed = NOW() " .
                     "where  process < NOW() and (status = 'QUEUED' or status = 'REQUEUED')");

    $db->query("commit");

    // restart/reload all requested services.
    $service->activate();
    if ($debug == 0 && substr($tmpdir, 0, 11) == "/tmp/astcfg") {
      system("rm -rf $tmpdir");
    }
  } else { // no activate records found
    if ($debug > 0) echo "IDLE\n";
    foreach ($modules as $key => $module) {
      $modules[$key]->idle();
    }
    sleep(1);
  }
}

// Remove pid file.
if(posix_getpid() == $pid) {
  unlink($pid_file);
}
?>
