#!/usr/bin/php
<?php
#FIXME : La fonction d'exit système
#TOUNDERSTAND : lors de la validation d'un ticket, le ticket est placé dans une bdd pour une heure
# Donc on n'en demande un nouveau que si celui-ci est expiré
//////////////////////////////////////////////////////////////////////////////////////////////////
/*                    CONFIGURATION                         */
//////////////////////////////////////////////////////////////////////////////////////////////////
include('authprog.conf');
define('LF', "\n");
// Définition des données ldap
$ldap = ldap_connect('ldap://'.LDAP_SERVER);
// Définition de l'utilisateur mail
define('MAIL_UID', 'mail');
define('MAIL_GID','mail');
// Les configurations Cas
require_once('CAS/eoleCAS.php');
require_once('configCAS/cas.inc.php');
define('__CAS_URL', '');
define('SERVICE', 'imap://'.IMAP_SERVER);
define('TIMEOUT', 3600); // Validité des tickets : 1 heure
// Données pour la base de données
define('CAS_BDD_PATH', '/var/lib/courier-eolecas/');
define('CAS_BDD', CAS_BDD_PATH.'casPT-cache');
define('CAS_TABLE', 'cas');
//////////////////////////////////////////////////////////////////////////////////////////////////
/*                                  FONCTIONS UTILES                                            */
//////////////////////////////////////////////////////////////////////////////////////////////////
function cbExit($code){
    // Fonction de sortie de la phase de connexion
    if (isset($ldap)){
        @ldap_unbind($ldap);
    }
    @sqlite_close();
    closelog();
    echo "FAIL" . LF;
    exit($code);
}
function _log($msg){
    // Fonction de log
    if (DEBUG){
        $msg = escapeshellarg($msg);
        $cmd = 'echo '. $msg . ' >> ' . LOGFILE;
        `$cmd`;
    }
}
function testLDAP($ldap){
    // Test la disponibilité de l'annuaire
    if (!ldap_bind($ldap)){
        _log(' - Annuaire ldap indisponible');
         cbExit(1);
    }
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/*         INITIALISATION DE LA BASE DE DONNÉES POUR LA GESTION DES TICKETS CAS         */
//////////////////////////////////////////////////////////////////////////////////////////////////
// Definition des données pour la bdd
_log('Debut');
define_syslog_variables();
// Définition de l'en-tête des logs pour syslog
openlog('IMAP Auth', $LOG_PID, $LOG_AUTHPRIV);
if (! is_dir(CAS_BDD_PATH)){
    mkdir(CAS_BDD_PATH);
}
$mustcreatetable = !file_exists(CAS_BDD);
if (!$db = sqlite_open(CAS_BDD)){
    _log('Impossible d\'ouvrir la bdd ['.CAS_BDD.'] !!!');
    syslog($LOG_ERR, 'Impossible d\'ouvrir la bdd ['.CAS_BDD.'] !!!');
    cbExit(1);
}
chmod(CAS_BDD, 0600);
if ($mustcreatetable){
    $sql = 'CREATE TABLE '.CAS_TABLE.'(';
    $sql .= 'pkey INTEGER AUTOINCREMENT,';
    $sql .= 'user VARCHAR(20) DEFAULT NULL,';
    $sql .= 'ticket VARCHAR(70) DEFAULT NULL,';
    $sql .= 'dv TIMESTAMP DEFAULT NULL,';
    $sql .= 'PRIMARY KEY(pkey))';
    sqlite_query($db, $sql);
    if ($err=sqlite_last_error($db)){
        _log("Erreur create table");
        syslog($LOG_ERR, sqlite_error_string($err));
        cbExit(1);
    }else{
        syslog($LOG_INFO, 'SQL ['. $sql . '] ok');
    }
}
closelog();
//////////////////////////////////////////////////////////////////////////////////////////////////
/*                FONCTION D'AUTHENTIFICATION CAS ET LDAP                */
//////////////////////////////////////////////////////////////////////////////////////////////////

function validateCAS($db, $user, $ticket){
    $now = time();
    // initialisation d'un client phpCAS (proxy/pas de gestion de la session)
    $client=new EoleCASClient(__CAS_VERSION, TRUE, __CAS_SERVER, __CAS_PORT, __CAS_URL, FALSE);
    if (DEBUG) EolephpCAS::setDebug(LOGFILE);
    $client->setNoCasServerValidation();
    $client->setURL(SERVICE);
    //EolephpCAS::setNoCasServerValidation();
    //EolephpCAS::setFixedServiceURL(SERVICE);
    // $PHPCAS_CLIENT=$GLOBALS['_PHPCAS_CLIENT']; // cf CAS-1.0.1/CAS.php ligne195
    // Ne traite que ce qui ressemble à un ticket CAS
    if (preg_match('/^PT-.*/', $ticket) || preg_match('/^ST-.*/', $ticket)){
        // Recherche d'un enregistrement où la validité (dv)
        // est plus grande que l'heure actuelle (t)
        $t = strftime('%Y-%m-%d %H:%M:%S', $now); // format SQL
        $sql="SELECT * FROM " . CAS_TABLE . " WHERE user='".$user."'";
        $sql .= " AND ticket='". $ticket . "'";
        $sql .= " AND dv>='".$t."'";
        $result = sqlite_query($db, $sql);
        if ($result){
            $nb = sqlite_num_rows($result);
            if ($nb == 1){ // OK : le ticket existe dans la bdd
                _log("  + Il y a un ticket valide dans la base de données");
                return true;
            } else {
                _log("  + Authentification auprès du serveur CAS");
                $client->setTicket($ticket);
                $validate_url = $client->getServerProxyValidateURL();
                // Autorisation du proxy pour permettre la validation du ticket PT
                $prox_chain = new CAS_ProxyChain(array(__CAS_SERVER));
                $client->getAllowedProxyChains()->allowProxyChain($prox_chain);
                // Valide le ticket auprès du serveur
                $res = $client->validateCAS20($validate_url,$text_response,$tree_response);
                if ($res){
                    _log("  + Le PT est validé, on le stocke dans la base.");
                    $dv=strftime('%Y-%m-%d %H:%M:%S', $now+TIMEOUT);
                    $sql = 'INSERT INTO '.CAS_TABLE;
                    $sql .= ' (user,ticket,dv) VALUES';
                    $sql .= ' (\'' . $user . '\', \''.$ticket.'\',\''.$dv.'\')';
                    return sqlite_query($db, $sql);
                }else{
                    _log("  + Le PT récupéré n'est pas valide");
                }
            }
        }
    }else{
        _log("   - Ceci n'est pas un PT : 'bad regexp'");
    }
    return false;
}
function validateLDAP($ldap, $user, $passwd){
    _log(" + Authentification Ldap");
    testLDAP($ldap);
    $filter = sprintf(USER_FILTER, $user);
    $sr = ldap_search($ldap, BASEDN, $filter, array('mail'));
    $infos = ldap_get_entries($ldap, $sr);
    if ((count($infos) > 0) && (isset($infos[0]['dn']))){
        $userdn = $infos[0]['dn'];
    }else{
        _log("   - Cet utilisateur est inconnu ou n'a pas de boite mail");
        cbExit(1);
    }
    _log($userdn);
    return ldap_bind($ldap, $userdn, $passwd);
}
////
//////////////////////////////////////////////////////////////////////////////////////////////////////
/////*             COMMUNICATION AVEC COURIER-AUTHDAEMON                    */
//////////////////////////////////////////////////////////////////////////////////////////////////////
// gestion du protocole authpipe
$cmd = fgets(STDIN);
$cmd = rtrim($cmd, LF);

function sendData($ldap, $user){
    // renvoie les données utilisateurs à une requete PRE
    testLDAP($ldap);
    $user = strtolower($user);
    $filter = sprintf(USER_FILTER, $user);
    // libelle des champs : nom de l'attr ldap -> nom pour courier-imap
    $fields = array('mail' => 'ADDRESS', 'description' => 'NAME', 'maildir'=>'MAILDIR', 'homedirectory'=>'HOME');
    $sr = ldap_search($ldap, BASEDN, $filter, array_keys($fields));
    if (!$sr){
        log("Erreur lors de la recherche auprès de l'annuaire");
         cbExit(1);
        }
    $n = ldap_count_entries($ldap, $sr);
    if ($n != 1){
        log("Erreur : plusieurs utilisateurs sont retournés par l'annuaire à la recherche de %s" % user);
        cbExit(1);
    }

    echo 'USERNAME=mail' . LF;
    echo 'UID=' . MAIL_UID. LF;
    echo 'GID='. MAIL_GID .LF;

    $entry = ldap_get_entries($ldap, $sr);
    $entry = $entry[0];
    ldap_free_result($sr);
    @ldap_unbind($ldap);
    // Hack pour les utilisateurs sans homedirectory ...
    #if ((!isset($entry['homedirectory'])) || ($entry['homedirectory'] == '')){
    #    $entry['homedirectory'][0] = '/var/spool/mail';
    #    $entry['homedirectory']['count'] = 1;
    #}
    // Hack 2.3 : tous les utilisateurs dans /home/mail
    $entry['homedirectory'][0] = '/home/mail';
    $entry['homedirectory']['count'] = 1;
    foreach ($entry as $attr=>$values){
        for ($j=0; $j<$values['count']; $j++){
            echo $fields[$attr] .'='. $values[$j] . LF;
        }
    }
    echo '.' . LF;
    cbExit(0);
}
// AUTHENTIFICATION
if (preg_match('/^PRE \. (\S+) (.*)$/', $cmd, $r)) // PRE . imap $username
{
    $service = $r[1];
    $user = $r[2];
    sendData($ldap, $user);
    cbExit(0);
}
elseif (preg_match('/^AUTH (\d+)$/', $cmd, $r))
    // AUTH $len
    //(ou $len est la longueur de la requete ex: 28 pour imap\nlogin\nusername\npassword)
{
    _log("# Authentification");
    $len = $r[1];
    $authdata = fread(STDIN, $len);
    if ($authdata == false){
        _log("   - Sortie");
         cbExit(1);
    }
    list($service, $authtype, $user, $password) = explode(LF, $authdata);
    $datas = "service : " . $service . 'user : '. $user . ' password : '. $password;
    _log("  + ".escapeshellarg($datas));
    switch($authtype){
        case 'login':
            $datas = 'user : '. $user . ' password : '. $password;
            $cas_res = validateCAS($db, $user, $password);
            if ($cas_res || validateLDAP($ldap, $user, $password)){
                sendData($ldap, $user);
                cbExit(0);
            }
            break;
        default:
            _log("   - Bad case : ".$authtype);
            cbExit(1);
            break;
    }
}
?>
