#!/usr/bin/php -q
<?php

// Copyright (c) 1997-now, NeoNova BV, The Netherlands. All rights Reserved.
//
// This software is the copyrighted work of NeoNova BV.
// Use of the software is subject to the general business terms and conditions of NeoNova BV
// and its related transaction documents like license sheets or other written notices.
//
// Any reproduction or redistribution of the software not in accordance with the license terms
// is expressly prohibited by law, and may result in severe civil and criminally penalties.
// Violators will be prosecuted to the maximum extent possible.

?>
<?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();

function getSystemMemInfo() 
{       
    $data = explode("\n", file_get_contents("/proc/meminfo"));
    $meminfo = array();
    foreach ($data as $line) {
        list($key, $val) = explode(":", $line);
        $meminfo[$key] = trim($val);
    }
    return $meminfo;
}

  // grab half the amount of RAM for our max use.
  $meminfo = getSystemMemInfo();
  $for_php = sprintf("%d", $meminfo['MemTotal'] / 2 > 0 ? $meminfo['MemTotal'] * 512 : 128000000);
  ini_set('memory_limit', $for_php);

// 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 $astiumconf;    // all values from /etc/sysconfig/astium
global $astglobalname; // astglobal name by id
global $astglobalcat;  // astglobal category by id
global $astiumid;      // id of the astium we're running on.
global $sess;          // contains product strings.

$pid_file = '/var/run/astcfgd.pid';

// Get command line options.
$options = getopt("hd:");
if (isset($options['h'])) {
  printf("usage: astcfgd [-h] [-d n]\n");
  exit(0);
}
$debug = $options["d"];

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

// Include pear modules.
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 copy_if_changed($source, $destination)
{
  $sha_src = sha1_file($source);
  if(file_exists($destination)) {
    $sha_dst = sha1_file($destination);
  } else {
    $sha_dst = "xxxxxx"; /* something clearly impossible */
  }
  
  //printf("%s: %s\n", $sha_src, $source);
  //printf("%s: %s\n", $sha_dst, $destination);
  if ($sha_src != $sha_dst) {  
    //system("diff -uNr $source $destination");
    copy($source, $destination);
    return true;
  }
  return false;
}

function become_daemon() {

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

}

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 remove_pid_file($file) {

  if(file_exists($file)) {
    $fp = fopen($file,"r");
    $pid = fgets($fp,1024);
    fclose($fp);

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

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;
   }
}

set_product(); // read product description strings
// Create filename temp directory.
date_default_timezone_set("Europe/Amsterdam");
$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/backup.php";
//include "/usr/local/astium/modules/postfix.php";
include "/usr/local/astium/modules/firewall.php";
include "/usr/local/astium/modules/fail2ban.php";
include "/usr/local/astium/modules/network.php";
include "/usr/local/astium/modules/astiumd.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/radiusd.php";
include "/usr/local/astium/modules/tftp.php";
include "/usr/local/astium/modules/dahdi.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";
//include "/usr/local/astium/modules/cyrus.php";
foreach (glob("/etc/asterisk/plugins/*.astcfgd.php") as $filename) {
  include $filename; 
}

// Include pbx apps astcfgd plugins.
if (is_dir('/usr/local/pbx/apps')) {
  foreach (glob("/usr/local/pbx/apps/*/plugins/astcfgd/astcfgd.php") as $filename) {
    include $filename; 
  }
}

// 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 
  $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 connection.
$act_q   = new DB_CMS;
$db      = new DB_CMS;
$tmp     = new DB_CMS;
$service = new services;

if ($debug > 3) $act_q->Debug = 1;
if ($debug > 2) $db->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'");

// Do not startup when running on a demo astium.
$db->query("select username from contact where id = '2'");
$db->next_record();
if ($db->f('username') == 'demo') {
  printf("system username is 'demo'. Quitting...\n");
  exit(0);
}

// 
// 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) {

  // Display memory usage.
  if ($debug > 3) {
    printf("%s memory used : ".memory_get_usage()."\n", date("M j H:i:s"));
  }

  $fh = fopen('/usr/local/astium/etc/astiumid', 'r');
  if ($fh) {
    $astiumid = (int) fread($fh, 20);
    fclose($fh);
    if ($astiumid == 0) {
      $fh = fopen('/usr/local/astium/etc/astiumid', 'w');
      fwrite($fh, '2');
      fclose($fh);
      $astiumid = 2;
    }
  } else {
    $fh = fopen('/usr/local/astium/etc/astiumid', 'w');
    fwrite($fh, '2');
    fclose($fh);
    $astiumid = 2;
  }
  if ($astiumid == 0) {
    printf("/usr/local/astium/etc/astiumid contains illegal value\n");
    exit(0);
  }
  //echo "astiumid = $astiumid\n";

  $lines = file('/etc/sysconfig/astium',  FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  if ($lines !== false) {
    foreach ($lines as $line) {
      if ($line[0] == '#') continue;
      $tmpvar = explode('=', $line);
      $astiumconf[$tmpvar[0]] = $tmpvar[1];
    } 
  }

  // If we just booted, we can just as well use the opportunity to regenerate pending dahdip changes...
  $uptime = explode(" ", file_get_contents('/proc/uptime'));
  if ($uptime[0] < 240) { // uptime < 5 minutes: we are booting
    $db->query("select dahdichanged from astium where id = $astiumid");
    $db->next_record();
    if ($db->f('dahdichanged') == 'yes') {
      $db->query("begin");
      $db->query("insert into activate (action, type, which, status, created, process) values ('modify', 'dahdi', '1', 'QUEUED', NOW(), NOW())");
      $db->query("update activate set process = NOW() where type = 'dahdi' and status = 'QUEUED'"); // any existing ones?
      $db->query("update astium set dahdichanged = 'activating' where id = '$astiumid'");
      $db->query("commit");
    }
  }

  // Here we proces the activate queue.
  // NOTE: there can only be one begin/commit pair on the database
  $db->query("begin");

  // limit amount of records to be processed so we wont consume too much memory. Not that any further records will NOT be processed AT ALL
  $act_q->query("select * from activate where (status='QUEUED' or status='REQUEUED') and process < now() order by id asc limit 5000");

  if ($act_q->num_rows() > 0) {
    unset($astglobals);
    unset($astglobalname);
    unset($astglobalcat);
    $db->query("select * from astglobals");
    while ($db->next_record()) {
//      if ($debug > 1) echo "astglobals['" . $db->f('name') . "'] = " . $db->f('value') . "\n";
      $astglobals[$db->f('name')] = $db->f('value');
      $astglobalname[$db->f('id')] = $db->f('name');
      $astglobalcat[$db->f('id')] = $db->f('category');
    }

    while ($act_q->next_record()) {
      $id     = $act_q->f('id');
      $action = $act_q->f('action');
      $type   = $act_q->f('type');
      $which  = $act_q->f('which');

      if ($action == '' || $type == '' || $which == '') {
          $db->query("rollback");
          $db->query("update activate " .
                     "set    status='ERROR', processed = NOW(), message = 'malformed activate record' " .
                      "where  id = '$id'");
          $rc = "ERROR";
          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) {
          $db->query("rollback");
          $db->query("update activate " .
                     "set    status='ERROR', processed = NOW(), message = 'record $which in table $table not found' " .
                     "where  id = '$id'");
          $rc = "ERROR";
          break;
        }
      }

      if ($astglobals['asterisk_realtime'] == 'yes') {
        if ($type == "user") {
          $tmp->query("select channel.username as username from channel, phone, contact where contact.id='$which' and 
                      (contact.phone=phone.id or contact.phone=phone.pairedwith) and phone.id=channel.phone and 
                      channel.inuse='yes' and channel.protocol='SIP';");
          while ($tmp->next_record()) {
            $service->request('asteriskrx', 'sip refresh peer '.$tmp->f('username'));
          }
        }
        else if ($type == "phone") {
          $tmp->query("select channel.username as username from channel, phone where (phone.id='$which' or phone.pairedwith='$which') and 
                       phone.id=channel.phone and channel.inuse='yes' and channel.protocol='SIP';");
          while ($tmp->next_record()) {
            $service->request('asteriskrx', 'sip refresh peer '.$tmp->f('username'));
          }
        }
        else if ($type == "phoneline") {
          $tmp->query("select channel.username from channel, phone, contact, phoneline where phoneline.id='270' and 
                       phoneline.function='contact' and phoneline.lookupid=contact.id and  (contact.phone=phone.id or 
                       contact.phone=phone.pairedwith) and phone.id=channel.phone and channel.inuse='yes' and channel.protocol='SIP';");
          while ($tmp->next_record()) {
            $service->request('asteriskrx', 'sip refresh peer '.$tmp->f('username'));
          }
        }
      }

      // give this activation to all modules, so they can check if
      // everything necessary for activation is present.
      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 == "REQUEUE") { // must be requeued
          if ($debug > 0) printf("during action $key: $rc\n");
          $db->query("update activate set status = 'REQUEUED' where id = '$id'");
          $rc = "OK";
        } else 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");
          $db->query("update activate " .
                     "set    status='ERROR', processed = NOW(), message = '$rc' " .
                     "where  id = '$id'");
          break 2; // We promissed to restart the loop.
        }
      }
      if ($debug > 0) printf("\n");
    }
    if ($rc != "OK") {
      continue;
    }
    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 ($rc != "OK") {
      $db->query("rollback");
      continue;
    }
    if ($debug > 0) printf("\n");

    if ($debug > 0) printf("STAGE ACTIVATE:\n");
    foreach ($modules as $key => $module) {
      if ($debug > 0) printf("%s ", $key);
      if ($key == 'services') continue; // this is a special case
      if ($key == 'asterisk') continue; // we'll do this one the last (because it reboots phones which require config)
      if ($key == 'asteriskrx') continue; // we'll do this one after the asterisk reload so skip here also
      $rc = $modules[$key]->activate();
      if ($rc != "OK") {
        if ($debug > 0) printf("unexpected error during activate: $rc\n");
        break;
      }
    }
    if ($rc != "OK") {
      $db->query("rollback");
      continue;
    }
    $rc = $modules['asterisk']->activate();
    if ($rc != "OK") {
      if ($debug > 0) printf("unexpected error during config: $rc\n");
      $db->query("rollback");
      continue;
    }

    if ($debug > 0) printf("\n");

    $db->query("update activate " .
               "set    status = 'DONE', message = 'OK', processed = NOW() " .
               "where  process < NOW() and status = 'QUEUED'");
    $db->query("update activate " .
               "set    status = 'QUEUED', process = NOW() + INTERVAL '5 seconds' " .
               "where  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 {
    if ($debug > 3) echo "IDLE\n";
    foreach ($modules as $key => $module) {
      $modules[$key]->idle();
    }
    $db->query("commit");
    sleep(1);
  }
}

// Removing our pid file.
remove_pid_file($pid_file);

?>
