1<?php
2/**
3 * DokuWiki Plugin smartcard (Auth Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Stephen Bowman <sbbowman@gmail.com>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12//class auth_plugin_authsmartcard extends DokuWiki_Auth_Plugin {
13class auth_plugin_authsmartcard extends auth_plugin_authplain {
14
15
16    /**
17     * Constructor.
18     */
19    public function __construct() {
20        parent::__construct(); // for compatibility
21
22        $this->cando['addUser']     = true; // can Users be created?
23        $this->cando['delUser']     = true; // can Users be deleted?
24        $this->cando['modLogin']    = true; // can login names be changed?
25        $this->cando['modPass']     = true; // can passwords be changed?
26        $this->cando['modName']     = true; // can real names be changed?
27        $this->cando['modMail']     = true; // can emails be changed?
28        $this->cando['modGroups']   = true; // can groups be changed?
29        $this->cando['getUsers']    = true; // can a (filtered) list of users be retrieved?
30        $this->cando['getUserCount']= true; // can the number of users be retrieved?
31        $this->cando['getGroups']   = true; // can a list of available groups be retrieved?
32        $this->cando['external']    = false; // does the module do external auth checking?
33        $this->cando['logout']      = false; // can the user logout again? (eg. not possible with HTTP auth)
34
35        $this->success = true;
36    }
37
38
39    /**
40     * Check user+password
41     *
42     * May be ommited if trustExternal is used.
43     *
44     * @param   string $user the user name
45     * @param   string $pass the clear text password
46     * @return  bool
47     */
48    public function checkPass(&$username, &$password) {
49
50	    session_start();
51	    // test if already logged in
52	    if(isset($_SESSION['smartcard_userdata']) && $_SESSION['smartcard_userdata']['username']==$username && md5($_SESSION['smartcard_userdata']['pass'])==$password){
53		    return true;
54	    }
55
56	    // client certificate log in
57	    if($username=='smartcard'){
58		// if client cert exists
59		if(isset($_SESSION['SSL_CLIENT_CERT']) && $_SESSION['SSL_CLIENT_CERT']){
60			// parse cert data
61			$client_cert_data   = openssl_x509_parse($_SESSION['SSL_CLIENT_CERT']);
62			$cn                     = $client_cert_data['subject']['CN'];
63			// if cn was in cert
64			if($cn){
65				$userdata = $this->findUserByCN($cn);
66				if(!$userdata){
67					$this->__log("Did not find user with cn=$cn");
68					msg('Did not find user with cn: '.$cn, -1);
69				}
70			}
71		}
72		// if client cert does not exist
73		else{
74			$this->__log("Client certificate not found");
75			msg('Smartcard was not found. Please check that it is connected and drivers are installed.', -1);
76			return false;
77		}
78	    }
79
80	    // SEE THAT WE GOT SMTH AND LOG HIM IN
81	    if($userdata){
82		    // overwrite username and password with what was found in the user db (notice, function was defined: checkPass(&$username, &$password))
83		    $username = $userdata['username'];
84		    $password = md5($userdata['pass']);
85		    // set $_SESSION['smartcard_userdata'] because otherwise auth will fail later on because invalid pw
86		    $_SESSION['smartcard_userdata'] = $userdata;
87		    session_write_close();
88		    return true;
89	    }
90
91	    // logon failed for some other unknown reason...
92	    unset($_SESSION['smartcard_userdata']);
93	    session_write_close();
94	    return false;
95    }
96
97    /**
98     * Finds user by cn
99     *
100     */
101    public function findUserByCN($cn){
102	    if(!$cn){
103		    $this->__log("passed an empty CN?");
104		    return false;
105	    }
106
107	    // retrieve all users where the CN is in the group for a user.
108	    $users = $this->retrieveUsers(0, 2000, array('grps'=>$cn));
109
110	    // if user count 1
111	    if(count($users)==1){
112		    // create username value for user
113		    foreach($users as $key => &$value){
114			    $value['username']  = $key;
115		    }
116		    $users  = array_values($users);
117		    $this->__log("Found user=" . $users[0]['username'] ." with CN=$cn");
118		    $this->__log(array($users[0]));
119		    return $users[0];
120	    }
121	    // if user count more than 1
122	    if(count($users)>1){
123		    $this->__log("Found multiple users with group having CN=$cn, that should not happen");
124		    return false;
125	    }
126	    // no users found
127	    $this->__log("$cn not found");
128	    return false;
129    }
130
131    /**
132     * Finds user by username and password
133     *
134     */
135    public function findUserByUsernameAndPassword($username, $password){
136	    $username = preg_replace('/[^\w\d\.-_]/', '', $username);
137	    $password = preg_replace('/[^\w\d\.-_]/', '', $password);
138
139	    $userdata = $this->getUserData($username);
140	    $userdata['username'] = $username;
141	    return $userdata;
142    }
143
144    /**
145     * Logs messages to data/log/auth_smartcard.log.txt
146     *
147     */
148    public function __log($text){
149
150	    $text = json_encode($text);
151
152	    if($this->getConf('log_to_file') && $this->getConf('logfile')){
153		    file_put_contents($this->getConf('logfile'), date('c').": ".$text."\n", FILE_APPEND);
154	    }
155    }
156
157    /**
158     * Return user info
159     *
160     * Returns info about the given user needs to contain
161     * at least these fields:
162     *
163     * name string  full name of the user
164     * mail string  email addres of the user
165     * grps array   list of groups the user is in
166     *
167     * @param   string $user the user name
168     * @return  array containing user data or false
169     */
170    public function getUserData($user) {
171        return parent::getUserData($user);
172    }
173
174    /**
175     * Create a new User [implement only where required/possible]
176     *
177     * Returns false if the user already exists, null when an error
178     * occurred and true if everything went well.
179     *
180     * The new user HAS TO be added to the default group by this
181     * function!
182     *
183     * Set addUser capability when implemented
184     *
185     * @param  string     $user
186     * @param  string     $pass
187     * @param  string     $name
188     * @param  string     $mail
189     * @param  null|array $grps
190     * @return bool|null
191     */
192    public function createUser($user, $pass, $name, $mail, $grps = null) {
193	    return parent::createUser($user, $pass, $name, $mail, $grps);
194    }
195
196    /**
197     * Modify user data [implement only where required/possible]
198     *
199     * Set the mod* capabilities according to the implemented features
200     *
201     * @param   string $user    nick of the user to be changed
202     * @param   array  $changes array of field/value pairs to be changed (password will be clear text)
203     * @return  bool
204     */
205    public function modifyUser($user, $changes) {
206	    return parent::modifyUser($user, $changes);
207    }
208
209    /**
210     * Delete one or more users [implement only where required/possible]
211     *
212     * Set delUser capability when implemented
213     *
214     * @param   array  $users
215     * @return  int    number of users deleted
216     */
217    public function deleteUsers($users) {
218	    return parent::deleteUsers($users);
219    }
220
221    /**
222     * Return a count of the number of user which meet $filter criteria
223     * [should be implemented whenever retrieveUsers is implemented]
224     *
225     * Set getUserCount capability when implemented
226     *
227     * @param  array $filter array of field/pattern pairs, empty array for no filter
228     * @return int
229     */
230    public function getUserCount($filter = array()) {
231	    return parent::getUserCount($filter);
232    }
233
234    /**
235     * Bulk retrieval of user data
236     *
237     * @param   int   $start index of first user to be returned
238     * @param   int   $limit max number of users to be returned
239     * @param   array $filter array of field/pattern pairs
240     * @return  array userinfo (refer getUserData for internal userinfo details)
241     */
242    public function retrieveUsers($start, $limit, $filter) {
243	    return parent::retrieveUsers($start, $limit, $filter);
244    }
245
246    /**
247     * Define a group [implement only where required/possible]
248     *
249     * Set addGroup capability when implemented
250     *
251     * @param   string $group
252     * @return  bool
253     */
254    public function addGroup($group) {
255	    return parent::addGroup();
256    }
257
258    /**
259     * Retrieve groups [implement only where required/possible]
260     *
261     * Set getGroups capability when implemented
262     *
263     * @param   int $start
264     * @param   int $limit
265     * @return  array
266     */
267    public function retrieveGroups($start = 0, $limit = 0) {
268	    return parent::retrieveGroups();
269    }
270
271    /**
272     * Return case sensitivity of the backend
273     *
274     * When your backend is caseinsensitive (eg. you can login with USER and
275     * user) then you need to overwrite this method and return false
276     *
277     * @return bool
278     */
279    public function isCaseSensitive() {
280	    return parent::isCaseSensitive();
281    }
282}
283
284// vim:ts=4:sw=4:et:
285