<?php
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();

/**
 * Plaintext authentication backend
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Todd Switzer <toddswitzer@gmail.com>
 */

if(!extension_loaded('radius')) {
        if (preg_match('/windows/i', getenv('OS'))) {
            dl('php_radius.dll');
        } else {
            dl('radius.so');
        }
}

class auth_plugin_authradius extends DokuWiki_Auth_Plugin {

    protected $radius = null;

    protected $classes;

    /**
     * Constructor
     *
     * Carry out sanity checks to ensure the object is
     * able to operate. Set capabilities.
     *
     */
    public function __construct() {
        parent::__construct();
        global $config_cascade;

	if(!function_exists('radius_add_server')) {
          msg("Radius err: PHP radius extension not found.",-1,__LINE__,__FILE__);
	  $this->success = false;
          return;
        }
	$host = $this->getConf('host');
        $port = $this->getConf('port');
        $secret = $this->getConf('secret');
        $timeout = $this->getConf('timeout');
        $tries = $this->getConf('tries');

        $hosts = explode(",", $host);
        for($i = 0; $i<count($hosts); $i++){
          $hosts[$i] = trim($hosts[$i]);
        }
        //create handle and add server
        $this->radius = radius_auth_open();

        //Setup radius servers
        for($i = 0; $i<count($hosts); $i++){
          if (!radius_add_server($this->radius,$hosts[$i],$port,$secret,$timeout,$tries)){
            msg("Radius err: ". radius_strerror($this->radius),-1,__LINE__,__FILE__);
            $this->success = false;
            return;
          }
        }
    }

    /**
     * Check user+password
     *
     * Checks if the given user exists and the given
     * plaintext password is correct
     *
     */
    public function checkPass($user, $pass) {
      if (! radius_create_request($this->radius,RADIUS_ACCESS_REQUEST)) {
        msg("Radius err: ". radius_strerror($this->radius),-1,__LINE__,__FILE__);
      }

      radius_put_attr($this->radius,RADIUS_USER_NAME,$user);
      
      $auth_type = $this->getConf('auth_type');        

      switch ($auth_type) {
        case 'PAP':
          radius_put_attr($this->radius,RADIUS_USER_PASSWORD,$pass);
          break;
          
        case 'MSCHAPV2':
          include_once('mschap.php');
          $auth_Challenge = GenerateChallenge(16);

          if (!radius_put_vendor_attr($this->radius, RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_CHALLENGE, $auth_Challenge)) {
            msg("RadiusError: RADIUS_MICROSOFT_MS_CHAP_CHALLENGE:" . radius_strerror($this->radius));
            exit;
          }
          
          $peer_Challenge = GeneratePeerChallenge();
          
          $ntresp = GenerateNTResponse($auth_Challenge, $peer_Challenge, $user, $pass);

          $reserved = str_repeat ("\0", 8);

          $resp = pack('CCa16a8a24', 1 , 1, $peer_Challenge, $reserved, $ntresp);
          
          if (!radius_put_vendor_attr($this->radius, RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP2_RESPONSE, $resp)) {
            msg("RadiusError: RADIUS_MICROSOFT_MS_CHAP2_RESPONSE:" . radius_strerror($this->radius));
            exit;
          }          
          
          break;
        default:
          radius_put_attr($this->radius,RADIUS_USER_PASSWORD,$pass);
          break;
      }

      //send the actual request and return result
      switch (radius_send_request($this->radius)) {
        case RADIUS_ACCESS_ACCEPT:
          $data = $this->getUserData($user);

          // Use class as group
          $this->classes[$user] = null;
          while ($resa = radius_get_attr($this->radius)) {
            if (!is_array($resa)) {
              msg("Error getting attribute: %s\n",  radius_strerror($this->radius));
              exit;
            }
            $attr = $resa['attr'];
            $data = $resa['data'];
            if($attr == RADIUS_CLASS){
              $this->classes[$user] = strtolower($data);
            }
          }

          return true;
          break;
        case RADIUS_ACCESS_REJECT:
          msg("Radius Error: " . radius_strerror($this->radius));
          return false;
          break;
        case RADIUS_ACCESS_CHALLENGE:
          msg("Radius: Challenge not supported by auth_radius.",-1);
          return false;
          break;
        default:
          msg('Radius Error: ('.$user.') ' . radius_strerror($this->radius),-1,__LINE__,__FILE__);
      }
      return false;
    }

    /**
     * Return user info
     *
     * Returns info about the given user needs to contain
     * at least these fields:
     *
     * name string  full name of the user
     * mail string  email addres of the user
     * grps array   list of groups the user is in
     *
     * @param string $user
     * @return array|bool
     */
    public function getUserData($user) {
      $data['mail'] = $user.'@'.$this->getConf('mailhost');
      $data['name'] = $user;
      //Check for a cached group from when the password was checked
      if($this->classes[$user] != null) {
        msg("Radius provided group(s) " . $this->classes[$user]);
        $data['grps'] = split('!:|;|,!', strtolower($this->classes[$user]));
      } else {
        $data['grps'] = array($this->getConf('defaultgroup'));
      }
      return $data;
    }
}
