#!/usr/bin/php -dmemory_limit=65535000 -q
<?php

// Copyright (c) 1997-2009, 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();

// 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
global $astiumid;      // id of the astium we're running on.

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

// 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/backup.php";
include "/usr/local/astium/modules/postfix.php";
include "/usr/local/astium/modules/firewall.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/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";
#include "/usr/local/astium/modules/cyrus.php";
foreach (glob("/etc/asterisk/plugins/*.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;
$service = new services;

if ($debug > 2) $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'");

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

// 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 > 1) {
    print "Memory used : ".memory_get_usage()."\n";
  }

  $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";

  // Here we proces the activate queue.
  $db->query("begin");

  $act_q->query("select * from activate where (status='QUEUED' or status='REQUEUED') and process < now() order by id asc limit 100");

  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");
          $act_q->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) {
          $db->query("rollback");
          $act_q->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.
      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_q->query("update activate " .
                        "set    status='ERROR', processed = NOW(), message = '$rc' " .
                        "where  id = '$id'");
          break; // the foreach 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") {
      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)
      $rc = $modules[$key]->activate();
      if ($rc != "OK") {
        if ($debug > 0) printf("unexpected error during activate: $rc\n");
        break;
      }
    }
    if ($rc != "OK") {
      continue;
    }
    $rc = $modules['asterisk']->activate();
    if ($rc != "OK") {
      if ($debug > 0) printf("unexpected error during config: $rc\n");
      continue;
    }

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

    $act_q->query("update activate " .
                  "set    status = 'DONE', message = 'OK', processed = NOW() " .
                  "where  process < NOW() and (status = 'QUEUED' or status = 'REQUEUED')");
    $act_q->query("commit");
    $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 > 0) 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);

?>
