1<?php
2// must be run within Dokuwiki
3if(!defined('DOKU_INC')) die();
4
5/**
6 * Plaintext authentication backend
7 *
8 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9 * @author     Todd Switzer <toddswitzer@gmail.com>
10 */
11
12if(!extension_loaded('radius')) {
13        if (preg_match('/windows/i', getenv('OS'))) {
14            dl('php_radius.dll');
15        } else {
16            dl('radius.so');
17        }
18}
19
20class auth_plugin_authradius extends DokuWiki_Auth_Plugin {
21
22    protected $radius = null;
23
24    protected $classes;
25
26    /**
27     * Constructor
28     *
29     * Carry out sanity checks to ensure the object is
30     * able to operate. Set capabilities.
31     *
32     */
33    public function __construct() {
34        parent::__construct();
35        global $config_cascade;
36
37	if(!function_exists('radius_add_server')) {
38          msg("Radius err: PHP radius extension not found.",-1,__LINE__,__FILE__);
39	  $this->success = false;
40          return;
41        }
42	$host = $this->getConf('host');
43        $port = $this->getConf('port');
44        $secret = $this->getConf('secret');
45        $timeout = $this->getConf('timeout');
46        $tries = $this->getConf('tries');
47
48        $hosts = explode(",", $host);
49        for($i = 0; $i<count($hosts); $i++){
50          $hosts[$i] = trim($hosts[$i]);
51        }
52        //create handle and add server
53        $this->radius = radius_auth_open();
54
55        //Setup radius servers
56        for($i = 0; $i<count($hosts); $i++){
57          if (!radius_add_server($this->radius,$hosts[$i],$port,$secret,$timeout,$tries)){
58            msg("Radius err: ". radius_strerror($this->radius),-1,__LINE__,__FILE__);
59            $this->success = false;
60            return;
61          }
62        }
63    }
64
65    /**
66     * Check user+password
67     *
68     * Checks if the given user exists and the given
69     * plaintext password is correct
70     *
71     */
72    public function checkPass($user, $pass) {
73      if (! radius_create_request($this->radius,RADIUS_ACCESS_REQUEST)) {
74        msg("Radius err: ". radius_strerror($this->radius),-1,__LINE__,__FILE__);
75      }
76
77      radius_put_attr($this->radius,RADIUS_USER_NAME,$user);
78
79      $auth_type = $this->getConf('auth_type');
80
81      switch ($auth_type) {
82        case 'PAP':
83          radius_put_attr($this->radius,RADIUS_USER_PASSWORD,$pass);
84          break;
85
86        case 'MSCHAPV2':
87          include_once('mschap.php');
88          $auth_Challenge = GenerateChallenge(16);
89
90          if (!radius_put_vendor_attr($this->radius, RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP_CHALLENGE, $auth_Challenge)) {
91            msg("RadiusError: RADIUS_MICROSOFT_MS_CHAP_CHALLENGE:" . radius_strerror($this->radius));
92            exit;
93          }
94
95          $peer_Challenge = GeneratePeerChallenge();
96
97          $ntresp = GenerateNTResponse($auth_Challenge, $peer_Challenge, $user, $pass);
98
99          $reserved = str_repeat ("\0", 8);
100
101          $resp = pack('CCa16a8a24', 1 , 1, $peer_Challenge, $reserved, $ntresp);
102
103          if (!radius_put_vendor_attr($this->radius, RADIUS_VENDOR_MICROSOFT, RADIUS_MICROSOFT_MS_CHAP2_RESPONSE, $resp)) {
104            msg("RadiusError: RADIUS_MICROSOFT_MS_CHAP2_RESPONSE:" . radius_strerror($this->radius));
105            exit;
106          }
107
108          break;
109        default:
110          radius_put_attr($this->radius,RADIUS_USER_PASSWORD,$pass);
111          break;
112      }
113
114      //send the actual request and return result
115      switch (radius_send_request($this->radius)) {
116        case RADIUS_ACCESS_ACCEPT:
117          $data = $this->getUserData($user);
118
119          // Use class as group
120          $this->classes[$user] = null;
121          while ($resa = radius_get_attr($this->radius)) {
122            if (!is_array($resa)) {
123              msg("Error getting attribute: %s\n",  radius_strerror($this->radius));
124              exit;
125            }
126            $attr = $resa['attr'];
127            $data = $resa['data'];
128            if($attr == RADIUS_CLASS){
129              $this->classes[$user] = strtolower($data);
130            }
131          }
132
133          return true;
134          break;
135        case RADIUS_ACCESS_REJECT:
136          msg("Radius Error: " . radius_strerror($this->radius));
137          return false;
138          break;
139        case RADIUS_ACCESS_CHALLENGE:
140          msg("Radius: Challenge not supported by auth_radius.",-1);
141          return false;
142          break;
143        default:
144          msg('Radius Error: ('.$user.') ' . radius_strerror($this->radius),-1,__LINE__,__FILE__);
145      }
146      return false;
147    }
148
149    /**
150     * Return user info
151     *
152     * Returns info about the given user needs to contain
153     * at least these fields:
154     *
155     * name string  full name of the user
156     * mail string  email addres of the user
157     * grps array   list of groups the user is in
158     *
159     * @param string $user
160     * @return array|bool
161     */
162    public function getUserData($user) {
163      $data['mail'] = $user.'@'.$this->getConf('mailhost');
164      $data['name'] = $user;
165      //Check for a cached group from when the password was checked
166      if($this->classes[$user] != null) {
167        msg("Radius provided group(s) " . $this->classes[$user]);
168        $data['grps'] = split('!:|;|,!', strtolower($this->classes[$user]));
169      } else {
170        $data['grps'] = array($this->getConf('defaultgroup'));
171      }
172      return $data;
173    }
174}
175