<?php
/* 
 ----------------------------------------------------------------------
 AlternC - Web Hosting System
 Copyright (C) 2000-2012 by the AlternC Development Team.
 https://alternc.org/
 ----------------------------------------------------------------------
 LICENSE

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License (GPL)
 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.

 To read the license please visit http://www.gnu.org/copyleft/gpl.html
 ----------------------------------------------------------------------
 Purpose of file: Administrate members and rights.
 ----------------------------------------------------------------------
*/

/* ----------------------------------------------------------------- */

/**
* Manage the AlternC's account administration (create/edit/delete)
*/
class m_admin {


  /* ----------------------------------------------------------------- */
  /** $enabled tells if the logged user is super-admin or not
   */
  var $enabled=0;

  /* ----------------------------------------------------------------- */
  /** List of the controls made for each TLD
   *
   * $tldmode is used by the administration panel, while choosing
   * the authorized TLDs. It's an array of strings explaining the current state of the TLD.
   */
  public $tldmode=array();

  var $archive='';

  /**
   * Constructor
   * 
   * @global	type $db
   * @global	type $cuid
   */
  function m_admin() {
    global $db,$cuid;
    $db->query("SELECT su FROM membres WHERE uid='$cuid';");
    $db->next_record();
    $this->enabled=$db->f("su");

    $this->tldmode=array(
			 0 => _("This TLD is forbidden"),
			 1 => _("primary DNS is checked in WHOIS db"),
			 2 => _("primary & secondary DNS are checked in WHOIS db"),
			 3 => _("Domain must exist, but don't do any DNS check"),
			 4 => _("Domain can be installed, no check at all"),
			 5 => _("Domain can be installed, force NO DNS hosting"),
			 );
    $this->archive=variable_get('archive_del_data','','If folder specified html folder of deleted user is archived, else it is deleted. ');
  }
  /**
   * 
   * @global	type $mem
   * @global	type $cuid
   * @global	type $debug_alternc
   * @global	type $L_INOTIFY_UPDATE_DOMAIN
   * @return boolean|string
   */
  function hook_menu() {
    global $mem, $cuid, $debug_alternc, $L_INOTIFY_UPDATE_DOMAIN;
    if (!$mem->checkRight()) return false;

    $obj = array(
      'title'       => _("Administration"),
      'ico'         => 'images/admin.png',
      'link'        => 'toggle',
      'class'       => 'adminmenu',
      'pos'         => 10,
      'links'       => 
        array(
          array(
           'txt'   => _("Manage AlternC accounts"), 
           'url'   => 'adm_list.php',
           'class' => 'adminmenu'
          ),
          array(
           'txt'   => _("User Quotas"), 
           'url'   => 'quotas_users.php?mode=4',
           'class' => 'adminmenu'
          ),
        )
     ) ;

    if ($cuid == 2000) {
      $obj['links'][] = 
        array(
           'txt'   => _("Admin Control Panel"), 
           'url'   => 'adm_panel.php',
           'class' => 'adminmenu'
          );
      $obj['links'][] = 
        array(
           'txt'   => _("PhpMyAdmin"), 
           'url'   => '/alternc-sql/',
           'class' => 'adminmenu',
           'target' => '_blank',
          );
      $obj['links'][] = 
        array(
           'txt'   => ($debug_alternc->status)?_("Switch debug Off"):_("Switch debug On"),
           'url'   => "alternc_debugme.php?enable=".($debug_alternc->status?"0":"1"), 
           'class' => 'adminmenu'
          );
      if (empty($L_INOTIFY_UPDATE_DOMAIN) || file_exists("$L_INOTIFY_UPDATE_DOMAIN") ) {
        $obj['links'][] =
          array(
             'txt'     => _("Applying..."),
             'url'     => 'javascript:alert(\''._("Domain changes are already applying").'\');',
             'class'   => 'adminmenu',
            );
      } else {
        $obj['links'][] =
          array(
             'txt'     => _("Apply changes"),
             'url'     => 'adm_update_domains.php',
             'class'   => 'adminmenu',
             'onclick' => 'return confirm("'.addslashes(_("Server configuration changes are applied every 5 minutes. Do you want to do it right now?")).'");',
            );

      } // L_INOTIFY_UPDATE_DOMAIN

    } // cuid == 2000


    return $obj;
  }

  /**
   * 
   */
  function stop_if_jobs_locked() {
    if ( file_exists(ALTERNC_LOCK_JOBS)) {
      echo "There is a file ".ALTERNC_LOCK_JOBS."\n";
      echo "So no jobs are allowed\n";
      echo "Did you launch alternc.install ?\n";
      die();
    }
  }

  /**
   * return the uid of an alternc account
   * 
   * @global	type $db
   * @param type $login
   * @return null
   */
  function get_uid_by_login($login) {
    global $db;
    $db->query("SELECT uid FROM membres WHERE login='$login';");
    if (! $db->next_record()) {
      return null;
    }
    return $db->f('uid');
  }


  /**
   * Returns the known information about a hosted account
   * 
   * Returns all what we know about an account (contents of the tables
   *  <code>membres</code> et <code>local</code>)
   * Ckecks if the account is super-admin
   * 
   * @global	   type $err
   * @global	   type $db
   * @global	   string     $lst_users_properties
   * @param     int         $uid a unique integer identifying the account
   * @param     boolean     $recheck
   * @return array|boolean an associative array containing all the fields of the
   * table <code>membres</code> and <code>local</code> of the corresponding account.
   * Returns FALSE if an error occurs.
   */
  function get($uid,$recheck=false) {
    global $err,$db,$lst_users_properties;
    //    $err->log("admin","get",$uid);
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }

    if (!isset($lst_users_properties) || empty($lst_users_properties) || !is_array($lst_users_properties) || $recheck ) {
      $lst_users_properties=array();
      $db->query("
	SELECT 
		m.uid as muid, 
		l.*, 
		m.*, 
		parent.login as parentlogin,
		dbs.name as db_server_name,
		m.renewed + INTERVAL m.duration MONTH as expiry,
		CASE 
			WHEN m.duration IS NULL THEN 0 
			WHEN m.renewed + INTERVAL m.duration MONTH <= NOW() THEN 3	
			WHEN m.renewed <= NOW() THEN 2
		ELSE 1 END 'status'
		
	FROM membres as m 
		LEFT JOIN membres as parent ON (parent.uid = m.creator) 
		LEFT JOIN db_servers as dbs ON (m.db_server_id = dbs.id)
		LEFT JOIN local as l ON (m.uid = l.uid) ;");
       while ($db->next_record()) {
         $lst_users_properties[$db->f('muid')]=$db->Record;
       }
    }

    if ( !isset($lst_users_properties[$uid]) ) {
      if ( !$recheck ) {
        // don't exist, but is not a forced check. Do a forced check
        return $this->get($uid, true);
      } 
      $err->raise("admin",_("Account not found"));
      return false;
    }

    return $lst_users_properties[$uid];
  }


  /**
   * Returns the known information about a specific hosted account
   * 
   * Similar to get_list() but for creators/resellers.
   * 
   * @global	   type $err
   * @global	   type $db
   * @param     int     $uid
   * @return    boolean
   */
  function get_creator($uid) {
    global $err,$db;
    //    $err->log("admin","get",$uid);
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }

    $db->query("SELECT m.*, parent.login as parentlogin FROM membres as m LEFT JOIN membres as parent ON (parent.uid = m.creator) WHERE m.uid='$uid';");

    if ($db->num_rows()) {
      $db->next_record();
      $c=$db->Record;
    } else {
      $err->raise("admin",_("Account not found"));
      return false;
    }

    $db->query("SELECT * FROM local WHERE uid='$uid';");
    if ($db->num_rows()) {
      $db->next_record();
      reset($db->Record);
      while (list($key,$val)=each($db->Record)) {
	      $c[$key]=$val;
      }
    }

    $db->query("SELECT count(*) as nbcreated FROM membres WHERE creator='$uid';");
    if ($db->num_rows()) {
      $db->next_record();
      reset($db->Record);
      while (list($key,$val)=each($db->Record)) {
	      $c[$key]=$val;
      }
    }

    return $c;
  }


  /**
   * 
   * @global	type $db
   * @return boolean TRUE if there is only one admin account
   * (allow the program to prevent the destruction of the last admin account)
   */
  function onesu() {
    global $db;
    $db->query("SELECT COUNT(*) AS cnt FROM membres WHERE su=1");
    $db->next_record();
    return ($db->f("cnt")==1);
  }


  /** 
   * Returns the list of the hosted accounts
   * 
   * Returns all what we know about ALL the accounts (contents of the tables
   *  <code>membres</code> et <code>local</code>)
   * Check for super-admin accounts
   * @param
   * @return 
   * 
   * @global	   type $err
   * @global	   type $mem
   * @global	   type $cuid
   * @param     integer $all
   * @param     integer $creator
   * @param     string $pattern
   * @param     string $pattern_type
   * @return    boolean | array an associative array containing all the fields of the
   * table <code>membres</code> and <code>local</code> of all the accounts.
   * Returns FALSE if an error occurs.
   */
  function get_list($all=0,$creator=0,$pattern=FALSE,$pattern_type=FALSE) {
    global $err,$mem,$cuid;
    $err->log("admin","get_list");
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    $db=new DB_System();


    if ($pattern) {

  	if ($pattern_type === 'domaine') {

	   $request = 'SELECT compte AS uid FROM domaines WHERE 1';

	   if ($pattern && preg_match('/[.a-zA-Z0-9]+/', $pattern))
		$request .= sprintf(' AND domaine LIKE "%%%s%%"', $pattern);

	   if ($creator) 
		$request .= sprintf(' AND compte in (select uid from membres where creator = "%s" ) ', $creator);

	   if ($mem->user['uid']!=2000 && !$all)
		$request .= sprintf(' AND compte in (select uid from membres where creator = "%s") ', $cuid);


	   $request .= ' GROUP BY uid';

        } elseif ($pattern_type === 'login') {

	   $request = 'SELECT uid FROM membres WHERE 1';

           if ($pattern && preg_match('/[a-zA-Z0-9]+/', $pattern))
		$request .= sprintf(' AND login LIKE "%%%s%%"', $pattern);

	   if ($creator) 
		$request .= sprintf(' AND creator = "%s"', $creator);

	   if ($mem->user['uid']!=2000 && !$all)
		$request .= sprintf(' AND creator = "%s"', $cuid);

	   $request .= ' ORDER BY login;';

	} else {

 	   $err->raise("admin", _("Invalid pattern type provided. Are you even performing a legitimate action?"));
	   return FALSE;

        }

    } else {
  
	if ($creator)
	{
      	    // Limit listing to a specific reseller
     	    $request = "SELECT uid FROM membres WHERE creator='".$creator."' ORDER BY login;";
	} elseif ($mem->user['uid']==2000 || $all) {
	      $request = "SELECT uid FROM membres ORDER BY login;";
        } else {
              $request = "SELECT uid FROM membres WHERE creator='".$cuid."' ORDER BY login;";
        }
    }

    $db->query($request);

    if ($db->num_rows()) {
      $c=array();
      while ($db->next_record()) {
	$c[$db->f("uid")]=$this->get($db->f("uid"));
      }
      return $c;
    } else {
      return false;
    }
  }


  /**
   * Send an email to all AlternC's accounts
   * 
   * @global	   type $err
   * @global	   type $mem
   * @global	   type $cuid
   * @global	   type $db
   * @param     string  $subject    Subject of the email to send
   * @param     string  $message    Message to send
   * @param     string  $from       Expeditor of that email
   * @return    boolean
   */
  function mailallmembers($subject,$message,$from) {
    global $err,$mem,$cuid,$db;
    $err->log("admin","mailallmembers");
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    $subject=trim($subject);
    $message=trim($message);
    $from=trim($from);

    if (empty($subject) || empty($message) || empty($from) ){
      $err->raise("admin",_("Subject, message and sender are mandatory"));
      return false;
    }
    //@todo remove cf functions.php
    if (checkmail($from) != 0) {
      $err->raise("admin",_("Sender is syntaxically incorrect"));
      return false;
    }

    @set_time_limit(1200);
    $db->query("SELECT DISTINCT mail FROM membres WHERE mail!='';");
    while ($db->next_record()) {
      // Can't do BCC due to postfix limitation
      // FIXME: use phpmailer, far better for mass-mailing than sendmail (reply-to issue among others)
      mail($db->f('mail'), $subject, $message, null, "-f$from");
    }
    return true;
  }


  /** 
   * Returns an array with the known information about resellers (uid, login, number of accounts)
   * Does not include account 2000 in the list.
   * May only be called by the admin account (2000)
   * If there are no reseller accounts, returns an empty array.
   * 
   * @global    type $err
   * @global    type $mem
   * @global    type $cuid
   * @return    boolean
   */
  function get_creator_list() {
    global $err,$mem,$cuid;

    $creators = array();

    $err->log("admin","get_reseller_list");
    if (!$this->enabled || $cuid!=2000) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }

    $db=new DB_System();
    $db->query("SELECT DISTINCT creator FROM membres WHERE creator <> 0 ORDER BY creator ASC;");
    if ($db->num_rows()) {
      while ($db->next_record()) {
        $creators[] = $this->get_creator($db->f("creator"));
      }
    }
    $creators2 = array();
    foreach ($creators as $cc ) {
      $creators2[$cc['uid']] = $cc;
    }
    return $creators2;
  }

  /** 
   * Check if I am the creator of the member $uid
   * 
   * @global    type $err
   * @global    type $mem
   * @global    type $db
   * @global    type $cuid
   * @param     int     $uid   a unique integer identifying the account
   * @return    boolean         TRUE if I am the creator of that account. FALSE else.
   */
  function checkcreator($uid) {
    global $err,$mem,$db,$cuid;
    if ($cuid==2000) {
      return true;
    }
    $db->query("SELECT creator FROM membres WHERE uid='$uid';");
    $db->next_record();
    if ($db->Record["creator"]!=$cuid) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    return true;
  }

  /**
   * When the admin want to delegate a subdomain to an account
   * 
   * @global    m_mysql $db
   * @global    m_err   $err
   * @global    m_dom   $dom
   * @global    m_mem   $mem
   * @global    int     $cuid
   * @param     string $u
   * @param     string $domain_name
   * @return boolean
   */
  function add_shared_domain($u, $domain_name) {
    global $db,$err,$dom,$mem,$cuid;
    $err->log("admin","add_shared_domain",$u."/".$domain_name);

    if (! $mem->checkright() ) {
      $err->raise("admin",_("-- Only administrators can do that! --"));
      return false;
    } 

    // Check if this domain exist on this admin account
    if (! in_array($domain_name, $dom->enum_domains())) {
      $err->raise("admin",_("You don't seem to be allowed to delegate this domain"));
      $err->log("admin","add_shared_domain","domain not allowed");
      return false;
    } 

    // Clean the domain_name 
    $domain_name=preg_replace("/^\.\.*/", "", $domain_name);

    $mem->su($u);
    $dom->lock();
    // option : 1=hébergement dns, 1=noerase, empeche de modifier, 1=force
    $dom->add_domain($mem->user['login'].".".$domain_name,1,1,1);
    $dom->unlock();
    $mem->unsu();
    return true;
  }
    
  /* ----------------------------------------------------------------- */
  /** Creates a new hosted account
   *  
   * Creates a new hosted account (in the tables <code>membres</code>
   * and <code>local</code>). Prevents any manipulation of the account if
   * the account $mid is not super-admin.
   *
   * 
   * @global    m_err   $err
   * @global    m_quota $quota
   * @global    array   $classes
   * @global    int     $cuid
   * @global    m_mem   $mem
   * @global    string  $L_MYSQL_DATABASE
   * @global    string  $L_MYSQL_LOGIN
   * @global    m_hooks $hooks
   * @global    m_action $action
   * @param     string  $login          Login name like [a-z][a-z0-9]*
   * @param     string  $pass           Password (max. 64 characters)
   * @param     string  $nom            Name of the account owner
   * @param     string  $prenom         First name of the account owner
   * @param     string  $mail           Email address of the account owner, useful to get
   *                                    one's lost password
   * @param     integer $canpass
   * @param     string  $type           Account type for quotas
   * @param     int     $duration
   * @param     string  $notes
   * @param     integer $force
   * @param     string  $create_dom
   * @param     int     $db_server_id
   * @return boolean Returns FALSE if an error occurs, TRUE if not.
   */
  function add_mem($login, $pass, $nom, $prenom, $mail, $canpass=1, $type='default', $duration=0, $notes = "", $force=0, $create_dom='', $db_server_id) {
    global $err,$quota,$classes,$cuid,$mem,$L_MYSQL_DATABASE,$L_MYSQL_LOGIN,$hooks,$action;
    $err->log("admin","add_mem",$login."/".$mail);
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    if (empty($db_server_id)) {
      $err->raise("admin",_("Missing db_server field"));
      return false;
    }
    if (($login=="")||($pass=="")) {
      $err->raise("admin",_("All fields are mandatory"));
      return false;
    }
    if (!$force) {
      if ($mail=="") {
	$err->raise("admin",_("All fields are mandatory"));
	      return false;
      }
        //@todo remove cf functions.php
      if (checkmail($mail)!=0){
	$err->raise("admin",_("Please enter a valid email address"));
	      return false;
      }
    }
    $login=strtolower($login);
    if (!preg_match("#^[a-z0-9]+$#",$login)) { //$
      $err->raise("admin", _("Login can only contains characters a-z and 0-9"));
      return false;
    }
    if (strlen($login) > 14) {
      // Not an arbitrary value : MySQL user names can be up to 16 characters long
      // If we want to allow people to create a few mysql_user (and we want to!)
      // we have to limit the login lenght
      $err->raise("admin",_("The login is too long (14 chars max)"));
      return false;
    }
    // Some login are not allowed...
    if ($login==$L_MYSQL_DATABASE || $login==$L_MYSQL_LOGIN || $login=="mysql" || $login=="root") {
      $err->raise("admin",_("Login can only contains characters a-z, 0-9 and -"));
      return false;
    }
    $pass=_md5cr($pass);
    $db=new DB_System();
    $notes = mysql_real_escape_string($notes);
    // Already exist?
    $db->query("SELECT count(*) AS cnt FROM membres WHERE login='$login';");
    $db->next_record();
    if (!$db->f("cnt")) {
      $db->query("SELECT max(m.uid)+1 as nextid FROM membres m");
      if (!$db->next_record()) {
	      $uid=2000;
      } else {
	      $uid=$db->Record["nextid"];
	      if ($uid<=2000) $uid=2000;
      }
      $db->query("INSERT INTO membres (uid,login,pass,mail,creator,canpass,type,created,notes,db_server_id) VALUES ('$uid','$login','$pass','$mail','$cuid','$canpass', '$type', NOW(), '$notes', '$db_server_id');");
      $db->query("INSERT INTO local(uid,nom,prenom) VALUES('$uid','$nom','$prenom');");
      $this->renew_update($uid, $duration);
      #exec("sudo /usr/lib/alternc/mem_add ".$login." ".$uid);
      $action->create_dir(getuserpath("$login"));
      $action->fix_user($uid);
      
      // Triggering hooks
      $mem->su($uid);
      // TODO: old hook method FIXME: when unused remove this
      /*
      foreach($classes as $c) {
      	if (method_exists($GLOBALS[$c],"alternc_add_member")) {
	        $GLOBALS[$c]->alternc_add_member();
	      }
      }
      */
      $hooks->invoke("alternc_add_member");
      // New hook way
      $hooks->invoke("hook_admin_add_member", array(), array('quota')); // First !!! The quota !!! Etherway, we can't be sure to be able to create all
      $hooks->invoke("hook_admin_add_member");
      $mem->unsu();

      if (!empty($create_dom)) { 
        $this->add_shared_domain($uid, $create_dom); 
      }

      return $uid;
    } else {
      $err->raise("admin",_("This login already exists"));
      return false;
    }
  }

  /** 
   * AlternC's standard function called when a user is created
   * This sends an email if configured through the interface.
   * 
   * @global    m_err   $err
   * @global    int     $cuid
   * @global    string     $L_FQDN
   * @global    string     $L_HOSTING
   * @return    boolean
   */
  function hook_admin_add_member() {
    global $err, $cuid, $L_FQDN, $L_HOSTING;
    $dest = variable_get('new_email', '0', 'An email will be sent to this address when new accounts are created if set.', array('desc'=>'Enabled','type'=>'boolean'));
    if (!$dest) {
      return false;
    }
    $db=new DB_System();
    if (!$db->query("SELECT m.*, parent.login as parentlogin FROM membres m LEFT JOIN membres parent ON parent.uid=m.creator WHERE m.uid='$cuid'")) {
      $err->raise("admin",sprintf(_("query failed: %s "), $db->Error));
      return false;
    }
    if ($db->next_record()) {
      // TODO: put that string into gettext ! 
      $mail = <<<EOF
A new AlternC account was created on %fqdn by %creator.

Account details
---------------

login: %login (%uid)
email: %mail
createor: %creator (%cuid)
can change password: %canpass
type: %type
notes: %notes
EOF;
       $mail = strtr($mail, array('%fqdn' => $L_FQDN,
       				  '%creator' => $db->Record['parentlogin'],
				  '%uid' => $db->Record['uid'],
				  '%login' => $db->Record['login'],
				  '%mail' => $db->Record['mail'],
				  '%cuid' => $db->Record['creator'],
				  '%canpass' => $db->Record['canpass'],
				  '%type' => $db->Record['type'],
				  '%notes' => $db->Record['notes']));
       $subject=sprintf(_("New account %s from %s on %s"), $db->Record['login'], $db->Record['parentlogin'], $L_HOSTING);
       if (mail($dest,$subject,$mail,"From: postmaster@$L_FQDN")) {
         //sprintf(_("Email successfully sent to %s"), $dest);
         return true;
       } else {
         $err->raise("admin",sprintf(_("Cannot send email to %s"), $dest));
         return false;
       } 
    } else {
       $err->raise("admin",sprintf(_("Query failed: %s"), $db->Error));
       return false;
    }
  }


  /** 
   * Edit an account
   *  
   * Change an account (in the tables <code>membres</code>
   * and <code>local</code>). Prevents any manipulation of the account if
   * the account $mid is not super-admin.
   *  
   * @global    m_err   $err
   * @global    m_mysql $db
   * @global    int     $cuid
   * @global    m_quota $quota
   * @param     int     $uid        The uid number of the account we want to modify
   * @param     string  $mail       New email address of the account owner
   * @param     string  $nom        New name of the account owner
   * @param     string  $prenom     New first name of the account owner
   * @param     string  $pass       New password (max. 64 characters)
   * @param     string  $enabled    (value: 0 or 1) activates or desactivates the
   * @param     boolean $canpass
   * @param     int     $type       New type of account
   * @param     int     $duration   
   * @param     string  $notes
   * @param     boolean $reset_quotas
   * @return    boolean Returns     FALSE if an error occurs, TRUE if not
   */
  function update_mem($uid, $mail, $nom, $prenom, $pass, $enabled, $canpass, $type='default', $duration=0, $notes = "",$reset_quotas=false) {
    global $err,$db;
    global $cuid, $quota;

    $notes=addslashes($notes);

    $err->log("admin","update_mem",$uid);
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    $db=new DB_System();
    if ($pass) {
      $pass=_md5cr($pass);
      $ssq=" ,pass='$pass' ";
    } else {
      $ssq="";
    }

    $old_mem = $this->get($uid);

    if (($db->query("UPDATE local SET nom='$nom', prenom='$prenom' WHERE uid='$uid';"))
	     &&($db->query("UPDATE membres SET mail='$mail', canpass='$canpass', enabled='$enabled', `type`='$type', notes='$notes' $ssq WHERE uid='$uid';"))){
      if($reset_quotas == "on" || $type != $old_mem['type'] ) {
        $quota->addquotas();
        $quota->synchronise_user_profile();
      }
      $this->renew_update($uid, $duration);
      return true;
    }
    else {
      $err->raise("admin",_("Account not found"));
      return false;
    }
  }


  /** 
   * Lock an account
   * 
   * Lock an account and prevent the user to access its account.
   * 
   * @global    m_err   $err
   * @global    m_mysql $db
   * @param     int     $uid    The uid number of the account 
   * @return    boolean         Returns FALSE if an error occurs, TRUE if not.
   */
  function lock_mem($uid) {
    global $err,$db;
    $err->log("admin","lock_mem",$uid);
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    $db=new DB_System();
    if ($db->query("UPDATE membres SET enabled='0' WHERE uid='$uid';")) {
      return true;
    }
    else {
      $err->raise("admin",_("Account not found"));
      return false;
    }
  }


  /** 
   * UnLock an account
   * 
   * UnLock an account and prevent the user to access its account.
   * 
   * 
   * @global    m_err   $err
   * @global    m_mysql $db
   * @param     int     $uid    The uid number of the account 
   * @return    boolean         Returns FALSE if an error occurs, TRUE if not.
   */
  function unlock_mem($uid) {
    global $err,$db;
    $err->log("admin","unlock_mem",$uid);
    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    $db=new DB_System();
    if ($db->query("UPDATE membres SET enabled='1' WHERE uid='$uid';")) {
      return true;
    }
    else {
      $err->raise("admin",_("Account not found"));
      return false;
    }
  }


  /* ----------------------------------------------------------------- */
  /** Deletes an account
   * Deletes the specified account. Prevents any manipulation of the account if
   * the account $mid is not super-admin.
   * 
   * @global    m_err   $err
   * @global    m_quota $quota
   * @global    array   $classes
   * @global    int     $cuid
   * @global    m_mem   $mem
   * @global    m_dom   $dom
   * @global    m_hooks $hooks
   * @global    m_action $action
   * @param     int     $uid    The uid number of the account 
   * @return    boolean         Returns FALSE if an error occurs, TRUE if not.
   */
  function del_mem($uid) {
    global $err,$quota,$classes,$cuid,$mem,$dom,$hooks,$action;
    $err->log("admin","del_mem",$uid);

    if (!$this->enabled) {
      $err->raise("admin",_("-- Only administrators can access this page! --"));
      return false;
    }
    $db=new DB_System();
    $tt=$this->get($uid);
    
    $mem->su($uid);
    // This script may take a long time on big accounts, let's give us some time ... Fixes 1132
    @set_time_limit(0);
    // WE MUST call m_dom before all others because of conflicts ...
    $dom->hook_admin_del_member();

    # New way of deleting or backup delted user html folders using action class
    $path=getuserpath($tt['login']);
    $action->archive($path);

    $hooks->invoke("alternc_del_member");
    $hooks->invoke("hook_admin_del_member");
    
    if (($db->query("DELETE FROM membres WHERE uid='$uid';")) &&
	($db->query("DELETE FROM local WHERE uid='$uid';"))) {
      $mem->unsu();
      // If this user was (one day) an administrator one, he may have a list of his own accounts. Let's associate those accounts to nobody as a creator.
      $db->query("UPDATE membres SET creator=2000 WHERE creator='$uid';");
      return true;
    } else {
      $err->raise("admin",_("Account not found"));
      $mem->unsu();
      return false;
    }
  }


  /** 
   * Renew an account
   * 
   * Renew an account for its duration
   * 
   * @global    m_err   $err
   * @global    m_mysql $db
   * @param     int     $uid        The uid number of the account 
   * @param     int     $periods    The new duration, in months, of the account
   * @return    boolean             Returns FALSE if an error occurs, TRUE if not.
   */
  function renew_mem($uid, $periods=1) {
    global $err,$db;

    $periods = intval($periods);
    if($periods == 0)
      return false;

    $query = "UPDATE membres SET renewed = renewed + INTERVAL (duration * $periods) MONTH WHERE uid=${uid};";
    if ($db->query($query)) {
      return true;
    } else {
      $err->raise("admin",_("Account not found"));
      return false;
    }
  }


  /** 
   * Update the duration information for an account
   * 
   * @global    m_err   $err
   * @global    m_mysql $db
   * @param     int     $uid        The uid number of the account 
   * @param     int     $duration   The new duration, in months, of the account
   * @return    boolean             Returns FALSE if an error occurs, TRUE if not.
   */
  function renew_update($uid, $duration) {
    global $err,$db;

    if($duration == 0) {
      if($db->query("UPDATE membres SET duration = NULL, renewed = NULL WHERE uid=$uid;"))
	return true;
    } else {
      if($db->query("UPDATE membres SET duration = $duration WHERE uid=$uid") &&
	 $db->query("UPDATE membres SET renewed = NOW() WHERE uid=$uid and renewed is null;"))
	return true;
    }

    $err->raise("admin",_("Account not found"));
    return false;
  }


  /** 
   * Get the expiry date for an account
   * 
   * @param     int     $uid        The uid number of the account 
   * @return    string              The expiry date, a string as printed by MySQL
   */
  function renew_get_expiry($uid) {
    $jj=$this->get($uid);
    if ( isset($jj) && isset($jj['expiry']) && ! empty($jj['expiry']) ) {
      return $jj['expiry'];
    }
    return '';
  }


  /* ----------------------------------------------------------------- */
  /** 
   * Get the expiry status for an account
   * 
   * @param     int     $uid        The uid number of the account 
   * @return    integer             The expiry status:
   *  0: account does not expire
   *  1: expires in more than duration,
   *  2: expires within the duration
   *  3: has expired past the duration
   */
  function renew_get_status($uid) {
    $jj=$this->get($uid);

    if ( isset($jj) && isset($jj['status']) && ! empty($jj['status']) ) {
      return $jj['status'];
    }

    return 0;
  }


  /** 
   * Get the expired/about to expire accounts.
   * 
   * @global    m_mysql $db
   * @return    array               The recordset of the corresponding accounts
   */
  function renew_get_expiring_accounts() {
    global $db;

    if(!$db->query("SELECT *, m.renewed + INTERVAL duration MONTH 'expiry'," .
		   " CASE WHEN m.duration IS NULL THEN 0" .
		   " WHEN m.renewed + INTERVAL m.duration MONTH <= NOW() THEN 3" .
		   " WHEN m.renewed <= NOW() THEN 2" .
		   " ELSE 1 END 'status' FROM membres m, local l" .
		   " WHERE m.uid = l.uid" .
		   " HAVING status=2 or status=3 ORDER BY status DESC, expiry;"))
      return false;
    else {
      $res=array();
      while($db->next_record())
	      $res[] = $db->Record;
      return $res;
    }
  }


  /** 
   * Turns a common account into a super-admin account
   * 
   * @global    m_err   $err
   * @global    m_mysql $db
   * @param     int     $uid        The uid number of the account 
   * @return    boolean    
   */
  function normal2su($uid) {
    global $err,$db;
    $db->query("SELECT su FROM membres WHERE uid='$uid';");
    if (!$db->next_record()) {
      $err->raise("admin",_("Account not found"));
      return false;
    } 
    if ($db->Record["su"]!=0) {
      $err->raise("admin",_("This account is ALREADY an administrator account"));
      return false;
    }
    $db->query("UPDATE membres SET su=1 WHERE uid='$uid';");
    return true;
  }


  /** 
   * Turns a super-admin account into a common account
   * 
   * @global    m_err   $err
   * @global    m_mysql $db
   * @param     int     $uid        The uid number of the account 
   * @return boolean                Returns FALSE if an error occurs, TRUE if not.
   */
  function su2normal($uid) {
    global $err,$db;
    $db->query("SELECT su FROM membres WHERE uid='$uid';");
    if (!$db->next_record()) {
      $err->raise("admin",_("Account not found"));
      return false;
    }
    if ($db->Record["su"]!=1) {
      $err->raise("admin",_("This account is NOT an administrator account!"));
      return false;
    }
    $db->query("UPDATE membres SET su=0 WHERE uid='$uid';");
    return true;
  }


  /**
   * List of the authorized TLDs
   * Returns the list of the authorized TLDs and also the way they are
   * authorized. A TLD is the last members (or the last two) of a
   * domain. For example, "com", "org" etc... AlternC keeps a table
   * containing the list of the TLDs authorized to be installed on the
   * server with the instructions to validate the installation of a
   * domain for each TLD (if necessary).
   * 
   * @global    m_mysql $db
   * @return    array   An associative array like $r["tld"], $r["mode"] where tld
   * is the tld and mode is the authorized mode.
   */
  function listtld() {
    global $db;
    $db->query("SELECT tld,mode FROM tld ORDER BY tld;");
    $c=array();
    while ($db->next_record()) {
      $c[]=$db->Record;
    }
    return $c;
  }


  /**
   * List the hosted domains on this server
   * 
   * Return the list of hosted domains on this server, (an array of associative arrays)
   * 
   * @global    m_mysql $db
   * @param     boolean     $alsocheck      Returns also errstr and errno telling the domains dig checks
   * @param     boolean     $forcecheck     Force the check of dig domain even if a cache exists.
   * @return array $r[$i] / [domaine][member][noerase][gesdns][gesmx]
   */
  function dom_list($alsocheck=false,$forcecheck=false) {
    global $db;
    $cachefile="/tmp/alternc_dig_check_cache";
    $cachetime=3600; // The dns cache file can be up to 1H old
    if ($alsocheck) {
      if (!$forcecheck && file_exists($cachefile) && filemtime($cachefile)+$cachetime>time()) {
	      $checked=unserialize(file_get_contents($cachefile));
      } else {
        // TODO : do the check here (cf checkdom.php) and store it in $checked
        $checked=$this->checkalldom();
        file_put_contents($cachefile,serialize($checked));
      }
    }
    $db->query("SELECT m.uid,m.login,d.domaine,d.gesdns,d.gesmx,d.noerase FROM domaines d LEFT JOIN membres m ON m.uid=d.compte ORDER BY domaine;");
    $c=array();
    while ($db->next_record()) {
      $tmp=$db->Record;
      if ($alsocheck) {
      	$tmp["errstr"]=$checked[$tmp["domaine"]]["errstr"];
	      $tmp["errno"]=$checked[$tmp["domaine"]]["errno"];
      }
      $c[]=$tmp;
    }
    return $c;
  }


  /** 
   * Check all the domains for their NS MX and IPs
   * 
   * @global    m_mysql $db
   * @global    string  $L_NS1
   * @global    string  $L_NS2
   * @global    string  $L_MX
   * @global    string  $L_PUBLIC_IP
   * @return    int
   */
  function checkalldom() {
    global $db,$L_NS1,$L_NS2,$L_MX,$L_PUBLIC_IP;
    $checked=array();
    $db->query("SELECT * FROM domaines ORDER BY domaine;");
    $dl=array();
    while ($db->next_record()) {
      $dl[$db->Record["domaine"]]=$db->Record;
    }
    sort($dl);
    foreach($dl as $c) {
      // For each domain check its type:
      $errno=0;
      $errstr="";
      $dontexist=false;
      // Check the domain.
      if ($c["gesdns"]==1) {
	      // Check the NS pointing to us
	      $out=array();
	      exec("dig +short NS ".escapeshellarg($c["domaine"]),$out);
	      if (count($out)==0) {
	        $dontexist=true;
	      } else {
	        if (!in_array($L_NS1.".",$out) || !in_array($L_NS2.".",$out)) {
	          $errno=1; $errstr.="NS for this domain are not $L_NS1 and $L_NS2 BUT ".implode(",",$out)."\n";
	        }
	      }
      }
      if ($c["gesmx"]==1 && !$dontexist) {
  	    $out=array();
	      exec("dig +short MX ".escapeshellarg($c["domaine"]),$out);
	      $out2=array();
	      foreach($out as $o) {
	        list($t,$out2[])=explode(" ",$o);
	      }
	      if (!in_array($L_MX.".",$out2)) {
	        $errno=1; $errstr.="MX is not $L_MX BUT ".implode(",",$out2)."\n";
	      }
      }
      if (!$dontexist) {
	      // We list all subdomains and check they are pointing to us.
	      $db->query("SELECT * FROM sub_domaines WHERE domaine='".addslashes($c["domaine"])."' ORDER BY sub;");
	      while ($db->next_record()) {
	        $d=$db->Record;
	        if ($d["type"]==0) {
	          // Check the IP: 
	          $out=array();
	          exec("dig +short A ".escapeshellarg($d["sub"].(($d["sub"]!="")?".":"").$c["domaine"]),$out);
                  if (! is_array($out)) { // exec dig can fail
                    $errno=1; $errstr.="Fail to get the DNS information. Try again.\n";
                  } else {
	            if (!in_array($L_PUBLIC_IP,$out)) {
	              $errstr.="subdomain '".$d["sub"]."' don't point to $L_PUBLIC_IP but to ".implode(",",$out)."\n";
	              $errno=1;
	            }
                  }
	        }
	      }
      }
      if ($dontexist) {
        $errno=2;
	      $errstr="Domain don't exist anymore !";
      }
      if ($errno==0) $errstr="OK";
      $checked[$c["domaine"]]=array("errno"=>$errno, "errstr"=>$errstr); 
    }
    return $checked;
  }


  /**
   * Lock / Unlock a domain 
   * 
   * Lock (or unlock) a domain, so that the member will be (not be) able to delete it
   * from its account
   * 
   * @global    m_mysql $db
   * @global    m_err   $err
   * @param     string  $domain     Domain name to lock / unlock
   * @return    boolean             TRUE if the domain has been locked/unlocked or FALSE if it does not exist.
   */
  function dom_lock($domain) {
    global $db,$err;
    $db->query("SELECT compte FROM domaines WHERE domaine='$domain';");
    if (!$db->next_record()) {
      $err->raise("dom",_("Domain '%s' not found."),$domain);
      return false;
    }
    $db->query("UPDATE domaines SET noerase=1-noerase WHERE domaine='$domain';");
    return true;
  }


  /** 
   * Add a new TLD to the list of the authorized TLDs 
   * 
   * @global    m_mysql $db
   * @global    m_err   $err
   * @param     string      $tld    top-level domain to add (org, com...)
   * @return    boolean             TRUE if the tld has been successfully added, FALSE if not.
   */
  function gettld($tld) {
    global $db,$err;
    $db->query("SELECT mode FROM tld WHERE tld='$tld';");
    if (!$db->next_record()) {
      $err->raise("admin",_("This TLD does not exist"));
      return false;
    }
    return $db->Record["mode"];
  }


  /** 
   * Prints the list of the actually authorized TLDs
   * 
   * @param     boolean $current   Value to select in the list
   */
  function selecttldmode($current=false) {
    for($i=0;$i<count($this->tldmode);$i++) {
      echo "<option value=\"$i\"";
      if ($current==$i) echo " selected=\"selected\"";
      echo ">"._($this->tldmode[$i])."</option>\n";
    }
  }


  /** 
   * Deletes the specified tld in the list of the authorized TLDs
   * <b>Note</b> : This function does not delete the domains depending
   * on this TLD
   * 
   * @global    m_mysql $db
   * @global    m_err   $err
   * @param     string  $tld   The TLD you want to delete
   * @return    boolean         returns true if the TLD has been deleted, or
   * false if an error occured.
   */
  function deltld($tld) {
    global $db,$err;
    $db->query("SELECT tld FROM tld WHERE tld='$tld';");
    if (!$db->next_record()) {
      $err->raise("admin",_("This TLD does not exist"));
      return false;
    }
    $db->query("DELETE FROM tld WHERE tld='$tld';");
    return true;
  }


  /* ----------------------------------------------------------------- */
  /** Add a TLD to the list of the authorized TLDs during the installation
   * 
   * <b>Note: </b> If you check in the whois, be sure that
   *  <code>m_domains</code> knows how to name the whois of the specified
   *  domain!
   * 
   * @global    m_mysql $db
   * @global    m_err   $err
   * @param     string  $tld        string TLD we want to authorize
   * @param     boolean $mode       Controls to make on this TLD.
   * @return    boolean             TRUE if the TLD has been successfully
   *  added. FALSE if not.
   */
  function addtld($tld,$mode) {
    global $db,$err;
    if (!$tld) {
      $err->raise("admin",_("The TLD name is mandatory"));
      return false;
    }
    $tld=trim($tld);

    $db->query("SELECT tld FROM tld WHERE tld='$tld';");
    if ($db->next_record()) {
      $err->raise("admin",_("This TLD already exist"));
      return false;
    }
    if (substr($tld,0,1)==".") $tld=substr($tld,1);
    $mode=intval($mode);
    if ($mode==0) $mode="0";
    $db->query("INSERT INTO tld (tld,mode) VALUES ('$tld','$mode');");
    return true;
  }


  /** 
   * Modify a TLD of the list of the authorized TLDs 
   * 
   * @global    m_mysql $db
   * @global    m_err   $err
   * @param     string  $tld    TLD we want to modify
   * @param     int     $mode   Controls to make on this TLD.
   * @return    boolean         TRUE if the TLD has been successfully
   * modified. FALSE if not.

   */
  function edittld($tld,$mode) {
    global $db,$err;
    $db->query("SELECT tld FROM tld WHERE tld='$tld';");
    if (!$db->next_record()) {
      $err->raise("admin",_("This TLD does not exist"));
      return false;
    }
    $mode=intval($mode);
    if ($mode==0) $mode="0";
    $db->query("UPDATE tld SET mode='$mode' WHERE tld='$tld';");
    return true;
  }


  /** 
   * Get the login name of the main administrator account
   * 
   * @global    m_mysql $db
   * @return string     the login name of admin, like 'root' for older alterncs
   */
  function getadmin() {
    global $db;
    $db->query("SELECT login FROM membres WHERE uid = '2000';");
    $db->next_record();
    return $db->f("login");
  }


  /** 
   * List the password policies currently installed in the policy table
   * 
   * @global    m_mysql $db
   * @global    array   $classes
   * @global    m_hooks $hooks
   * @return array              an indexed array of associative array from the MySQL "policy" table
   */
  function listPasswordPolicies() {
    global $db,$classes,$hooks;
    $tmp1=array();
    $tmp2=array();
    $policies=array();
    $db->query("SELECT * FROM policy;");
    while ($db->next_record()) {
      $tmp1[$db->Record["name"]]=$db->Record;
    }
/* * /
    foreach($classes as $c) {
      if (method_exists($GLOBALS[$c],"alternc_password_policy")) {
	$res=$GLOBALS[$c]->alternc_password_policy(); // returns an array
	foreach($res as $k=>$v) {
	  $tmp2[$k]=$v;
	}
      }
    }
/* */
    $tmp3=$hooks->invoke("alternc_password_policy");
    foreach ($tmp3 as $v) {
      foreach ($v as $l=>$m) {
        $tmp2[$l]=$m;
      }
    }
    foreach($tmp2 as $k=>$v) {
      if (!isset($tmp1[$k])) {
	// Default policy : 
	$db->query("INSERT INTO policy SET name='".addslashes($k)."', minsize=0, maxsize=64, classcount=0, allowlogin=0;");
	$tmp1[$k]=array(
			"minsize"=>0, "maxsize"=>64, "classcount"=>0, "allowlogin"=>0 
			);
      }
      $policies[$k]=$tmp1[$k];
      $policies[$k]["description"]=_($v);
      unset($tmp1[$k]);
    }
    foreach ($tmp1 as $k=>$v) {
      // Delete disabled modules :
      $db->query("DELETE FROM policy WHERE name='".addslashes($k)."';");
    }
    return $policies;
  }


  /** 
   * Change a password policy for one kind of password
   * 
   * @global    m_mysql $db
   * @param     string  $policy     Name of the policy to edit
   * @param     int     $minsize    Minimum Password size
   * @param     int     $maxsize    Maximum Password size
   * @param     int     $classcount How many class of characters must this password have
   * @param     boolean $allowlogin Do we allow the password to be like the login ? 
   * @return    boolean if the policy has been edited, or FALSE if an error occured.
   */
  function editPolicy($policy,$minsize,$maxsize,$classcount,$allowlogin) {
    global $db;
    $minsize=intval($minsize);
    $maxsize=intval($maxsize);
    $classcount=intval($classcount);
    $allowlogin=intval($allowlogin);

    $db->query("SELECT * FROM policy WHERE name='".addslashes($policy)."';");
    if (!$db->next_record()) {
      return false; // Policy not found
    }
    if ($minsize<0 || $minsize>64 || $maxsize<0 || $maxsize>64 || $maxsize<$minsize || $classcount<0 || $classcount>4) {
      return false; // Incorrect policy ...
    }      
    $allowlogin=($allowlogin)?1:0;
    $db->query("UPDATE policy SET minsize=$minsize, maxsize=$maxsize, classcount=$classcount, allowlogin=$allowlogin WHERE name='".addslashes($policy)."';");
    return true;
  }


  /**
   * 
   * @global    m_mysql $db
   * @global    m_err   $err
   * @param     string  $policy     Name of the policy to check for
   * @param     string  $login      The login that will be set
   * @param     string  $password   The password we have to check
   * @return    boolean             TRUE if the password if OK for this login and this policy, FALSE if it is not.
   */
  function checkPolicy($policy,$login,$password) {
    global $db,$err;

    if (empty($login)) {
      $err->raise("admin",_("Please enter a login"));
      return false;
    }
    if (empty($password)) {
      $err->raise("admin",_("Please enter a password"));
      return false;
    }

    $pol=$this->listPasswordPolicies();
    if (!$pol[$policy]) {
      $err->raise("admin",_("-- Program error -- The requested password policy does not exist!"));
      return false;
    }
    $pol=$pol[$policy];
    // Ok, now let's check it : 
    $plen=strlen($password);

    if ($plen<$pol["minsize"]) {
      $err->raise("admin",_("The password length is too short according to the password policy"));
      return false;
    }

    if ($plen>$pol["maxsize"]) {
      $err->raise("admin",_("The password is too long according to the password policy"));
      return false;
    }

    if (!$pol["allowlogin"]) {
      // We do misc check on password versus login : 
      $logins=explode("@",$login);
      $logins[]=$login;
      foreach($logins as $l) {
	if (strpos($password,$l)!==false) {
	  $err->raise("admin",_("The password policy prevents you to use your login name inside your password"));
	  return false;
	}
      }
    }

    if ($pol["classcount"]>0) {
      $cls=array(0,0,0,0,0);
      for($i=0;$i<strlen($password);$i++) {
	$p=substr($password,$i,1);
	if (strpos("abcdefghijklmnopqrstuvwxyz",$p)!==false) {
	  $cls[0]=1;
	} elseif (strpos("ABCDEFGHIJKLMNOPQRSTUVWXYZ",$p)!==false) {
	  $cls[1]=1;
	} elseif (strpos("0123456789",$p)!==false) {
	  $cls[2]=1;
	} elseif (strpos('!"#$%&\'()*+,-./:;<=>?@[\\]^_`',$p)!==false) {
	  $cls[3]=1;
	} else {
	  $cls[4]=1;
	}
      } // foreach
      $clc=array_sum($cls);
      if ($clc<$pol["classcount"]) {
	$err->raise("admin",_("Your password contains not enough different classes of character, between low-case, up-case, figures and special characters."));
	return false;
      }
    }
    return true; // congratulations ! 
  }


  /** 
   * hook function called by AlternC-upnp to know which open 
   * tcp or udp ports this class requires or suggests
   * 
   * @access private
   * @return array a key => value list of port protocol name mandatory values
   */
  function hook_upnp_list() {
    return array(
		 "http" => array("port" => 80, "protocol" => "tcp", "mandatory" => 1),
		 "https" => array("port" => 443, "protocol" => "tcp", "mandatory" => 0),
		 "ssh" => array("port" => 22, "protocol" => "tcp", "mandatory" => 0),
		 );
  }


} /* Classe ADMIN */