xref: /dokuwiki/lib/plugins/authpdo/auth.php (revision f64dbc90055403db700941e4691ea451bb971cef)
1*f64dbc90SAndreas Gohr<?php
2*f64dbc90SAndreas Gohr/**
3*f64dbc90SAndreas Gohr * DokuWiki Plugin authpdo (Auth Component)
4*f64dbc90SAndreas Gohr *
5*f64dbc90SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6*f64dbc90SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
7*f64dbc90SAndreas Gohr */
8*f64dbc90SAndreas Gohr
9*f64dbc90SAndreas Gohr// must be run within Dokuwiki
10*f64dbc90SAndreas Gohrif(!defined('DOKU_INC')) die();
11*f64dbc90SAndreas Gohr
12*f64dbc90SAndreas Gohrclass auth_plugin_authpdo extends DokuWiki_Auth_Plugin {
13*f64dbc90SAndreas Gohr
14*f64dbc90SAndreas Gohr    /** @var PDO */
15*f64dbc90SAndreas Gohr    protected $pdo;
16*f64dbc90SAndreas Gohr
17*f64dbc90SAndreas Gohr    /**
18*f64dbc90SAndreas Gohr     * Constructor.
19*f64dbc90SAndreas Gohr     */
20*f64dbc90SAndreas Gohr    public function __construct() {
21*f64dbc90SAndreas Gohr        parent::__construct(); // for compatibility
22*f64dbc90SAndreas Gohr
23*f64dbc90SAndreas Gohr        if(!class_exists('PDO')) {
24*f64dbc90SAndreas Gohr            $this->_debug('PDO extension for PHP not found.', -1, __LINE__);
25*f64dbc90SAndreas Gohr            $this->success = false;
26*f64dbc90SAndreas Gohr            return;
27*f64dbc90SAndreas Gohr        }
28*f64dbc90SAndreas Gohr
29*f64dbc90SAndreas Gohr        if(!$this->getConf('dsn')) {
30*f64dbc90SAndreas Gohr            $this->_debug('No DSN specified', -1, __LINE__);
31*f64dbc90SAndreas Gohr            $this->success = false;
32*f64dbc90SAndreas Gohr            return;
33*f64dbc90SAndreas Gohr        }
34*f64dbc90SAndreas Gohr
35*f64dbc90SAndreas Gohr        try {
36*f64dbc90SAndreas Gohr            $this->pdo = new PDO(
37*f64dbc90SAndreas Gohr                $this->getConf('dsn'),
38*f64dbc90SAndreas Gohr                $this->getConf('user'),
39*f64dbc90SAndreas Gohr                $this->getConf('pass'),
40*f64dbc90SAndreas Gohr                array(
41*f64dbc90SAndreas Gohr                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
42*f64dbc90SAndreas Gohr                )
43*f64dbc90SAndreas Gohr            );
44*f64dbc90SAndreas Gohr        } catch(PDOException $e) {
45*f64dbc90SAndreas Gohr            $this->_debug($e);
46*f64dbc90SAndreas Gohr            $this->success = false;
47*f64dbc90SAndreas Gohr            return;
48*f64dbc90SAndreas Gohr        }
49*f64dbc90SAndreas Gohr
50*f64dbc90SAndreas Gohr        // FIXME set capabilities accordingly
51*f64dbc90SAndreas Gohr        //$this->cando['addUser']     = false; // can Users be created?
52*f64dbc90SAndreas Gohr        //$this->cando['delUser']     = false; // can Users be deleted?
53*f64dbc90SAndreas Gohr        //$this->cando['modLogin']    = false; // can login names be changed?
54*f64dbc90SAndreas Gohr        //$this->cando['modPass']     = false; // can passwords be changed?
55*f64dbc90SAndreas Gohr        //$this->cando['modName']     = false; // can real names be changed?
56*f64dbc90SAndreas Gohr        //$this->cando['modMail']     = false; // can emails be changed?
57*f64dbc90SAndreas Gohr        //$this->cando['modGroups']   = false; // can groups be changed?
58*f64dbc90SAndreas Gohr        //$this->cando['getUsers']    = false; // can a (filtered) list of users be retrieved?
59*f64dbc90SAndreas Gohr        //$this->cando['getUserCount']= false; // can the number of users be retrieved?
60*f64dbc90SAndreas Gohr        //$this->cando['getGroups']   = false; // can a list of available groups be retrieved?
61*f64dbc90SAndreas Gohr        //$this->cando['external']    = false; // does the module do external auth checking?
62*f64dbc90SAndreas Gohr        //$this->cando['logout']      = true; // can the user logout again? (eg. not possible with HTTP auth)
63*f64dbc90SAndreas Gohr
64*f64dbc90SAndreas Gohr        // FIXME intialize your auth system and set success to true, if successful
65*f64dbc90SAndreas Gohr        $this->success = true;
66*f64dbc90SAndreas Gohr    }
67*f64dbc90SAndreas Gohr
68*f64dbc90SAndreas Gohr    /**
69*f64dbc90SAndreas Gohr     * Check user+password
70*f64dbc90SAndreas Gohr     *
71*f64dbc90SAndreas Gohr     * May be ommited if trustExternal is used.
72*f64dbc90SAndreas Gohr     *
73*f64dbc90SAndreas Gohr     * @param   string $user the user name
74*f64dbc90SAndreas Gohr     * @param   string $pass the clear text password
75*f64dbc90SAndreas Gohr     * @return  bool
76*f64dbc90SAndreas Gohr     */
77*f64dbc90SAndreas Gohr    public function checkPass($user, $pass) {
78*f64dbc90SAndreas Gohr
79*f64dbc90SAndreas Gohr        $data = $this->_selectUser($user);
80*f64dbc90SAndreas Gohr        if($data == false) return false;
81*f64dbc90SAndreas Gohr
82*f64dbc90SAndreas Gohr        if(isset($data['hash'])) {
83*f64dbc90SAndreas Gohr            // hashed password
84*f64dbc90SAndreas Gohr            $passhash = new PassHash();
85*f64dbc90SAndreas Gohr            return $passhash->verify_hash($pass, $data['hash']);
86*f64dbc90SAndreas Gohr        } else {
87*f64dbc90SAndreas Gohr            // clear text password in the database O_o
88*f64dbc90SAndreas Gohr            return ($pass == $data['clear']);
89*f64dbc90SAndreas Gohr        }
90*f64dbc90SAndreas Gohr    }
91*f64dbc90SAndreas Gohr
92*f64dbc90SAndreas Gohr    /**
93*f64dbc90SAndreas Gohr     * Return user info
94*f64dbc90SAndreas Gohr     *
95*f64dbc90SAndreas Gohr     * Returns info about the given user needs to contain
96*f64dbc90SAndreas Gohr     * at least these fields:
97*f64dbc90SAndreas Gohr     *
98*f64dbc90SAndreas Gohr     * name string  full name of the user
99*f64dbc90SAndreas Gohr     * mail string  email addres of the user
100*f64dbc90SAndreas Gohr     * grps array   list of groups the user is in
101*f64dbc90SAndreas Gohr     *
102*f64dbc90SAndreas Gohr     * @param   string $user the user name
103*f64dbc90SAndreas Gohr     * @param   bool $requireGroups whether or not the returned data must include groups
104*f64dbc90SAndreas Gohr     * @return array containing user data or false
105*f64dbc90SAndreas Gohr     */
106*f64dbc90SAndreas Gohr    public function getUserData($user, $requireGroups = true) {
107*f64dbc90SAndreas Gohr        $data = $this->_selectUser($user);
108*f64dbc90SAndreas Gohr        if($data == false) return false;
109*f64dbc90SAndreas Gohr
110*f64dbc90SAndreas Gohr        if($requireGroups) {
111*f64dbc90SAndreas Gohr
112*f64dbc90SAndreas Gohr        }
113*f64dbc90SAndreas Gohr
114*f64dbc90SAndreas Gohr        return $data;
115*f64dbc90SAndreas Gohr    }
116*f64dbc90SAndreas Gohr
117*f64dbc90SAndreas Gohr
118*f64dbc90SAndreas Gohr    /**
119*f64dbc90SAndreas Gohr     * Create a new User [implement only where required/possible]
120*f64dbc90SAndreas Gohr     *
121*f64dbc90SAndreas Gohr     * Returns false if the user already exists, null when an error
122*f64dbc90SAndreas Gohr     * occurred and true if everything went well.
123*f64dbc90SAndreas Gohr     *
124*f64dbc90SAndreas Gohr     * The new user HAS TO be added to the default group by this
125*f64dbc90SAndreas Gohr     * function!
126*f64dbc90SAndreas Gohr     *
127*f64dbc90SAndreas Gohr     * Set addUser capability when implemented
128*f64dbc90SAndreas Gohr     *
129*f64dbc90SAndreas Gohr     * @param  string $user
130*f64dbc90SAndreas Gohr     * @param  string $pass
131*f64dbc90SAndreas Gohr     * @param  string $name
132*f64dbc90SAndreas Gohr     * @param  string $mail
133*f64dbc90SAndreas Gohr     * @param  null|array $grps
134*f64dbc90SAndreas Gohr     * @return bool|null
135*f64dbc90SAndreas Gohr     */
136*f64dbc90SAndreas Gohr    //public function createUser($user, $pass, $name, $mail, $grps = null) {
137*f64dbc90SAndreas Gohr    // FIXME implement
138*f64dbc90SAndreas Gohr    //    return null;
139*f64dbc90SAndreas Gohr    //}
140*f64dbc90SAndreas Gohr
141*f64dbc90SAndreas Gohr    /**
142*f64dbc90SAndreas Gohr     * Modify user data [implement only where required/possible]
143*f64dbc90SAndreas Gohr     *
144*f64dbc90SAndreas Gohr     * Set the mod* capabilities according to the implemented features
145*f64dbc90SAndreas Gohr     *
146*f64dbc90SAndreas Gohr     * @param   string $user nick of the user to be changed
147*f64dbc90SAndreas Gohr     * @param   array $changes array of field/value pairs to be changed (password will be clear text)
148*f64dbc90SAndreas Gohr     * @return  bool
149*f64dbc90SAndreas Gohr     */
150*f64dbc90SAndreas Gohr    //public function modifyUser($user, $changes) {
151*f64dbc90SAndreas Gohr    // FIXME implement
152*f64dbc90SAndreas Gohr    //    return false;
153*f64dbc90SAndreas Gohr    //}
154*f64dbc90SAndreas Gohr
155*f64dbc90SAndreas Gohr    /**
156*f64dbc90SAndreas Gohr     * Delete one or more users [implement only where required/possible]
157*f64dbc90SAndreas Gohr     *
158*f64dbc90SAndreas Gohr     * Set delUser capability when implemented
159*f64dbc90SAndreas Gohr     *
160*f64dbc90SAndreas Gohr     * @param   array $users
161*f64dbc90SAndreas Gohr     * @return  int    number of users deleted
162*f64dbc90SAndreas Gohr     */
163*f64dbc90SAndreas Gohr    //public function deleteUsers($users) {
164*f64dbc90SAndreas Gohr    // FIXME implement
165*f64dbc90SAndreas Gohr    //    return false;
166*f64dbc90SAndreas Gohr    //}
167*f64dbc90SAndreas Gohr
168*f64dbc90SAndreas Gohr    /**
169*f64dbc90SAndreas Gohr     * Bulk retrieval of user data [implement only where required/possible]
170*f64dbc90SAndreas Gohr     *
171*f64dbc90SAndreas Gohr     * Set getUsers capability when implemented
172*f64dbc90SAndreas Gohr     *
173*f64dbc90SAndreas Gohr     * @param   int $start index of first user to be returned
174*f64dbc90SAndreas Gohr     * @param   int $limit max number of users to be returned
175*f64dbc90SAndreas Gohr     * @param   array $filter array of field/pattern pairs, null for no filter
176*f64dbc90SAndreas Gohr     * @return  array list of userinfo (refer getUserData for internal userinfo details)
177*f64dbc90SAndreas Gohr     */
178*f64dbc90SAndreas Gohr    //public function retrieveUsers($start = 0, $limit = -1, $filter = null) {
179*f64dbc90SAndreas Gohr    // FIXME implement
180*f64dbc90SAndreas Gohr    //    return array();
181*f64dbc90SAndreas Gohr    //}
182*f64dbc90SAndreas Gohr
183*f64dbc90SAndreas Gohr    /**
184*f64dbc90SAndreas Gohr     * Return a count of the number of user which meet $filter criteria
185*f64dbc90SAndreas Gohr     * [should be implemented whenever retrieveUsers is implemented]
186*f64dbc90SAndreas Gohr     *
187*f64dbc90SAndreas Gohr     * Set getUserCount capability when implemented
188*f64dbc90SAndreas Gohr     *
189*f64dbc90SAndreas Gohr     * @param  array $filter array of field/pattern pairs, empty array for no filter
190*f64dbc90SAndreas Gohr     * @return int
191*f64dbc90SAndreas Gohr     */
192*f64dbc90SAndreas Gohr    //public function getUserCount($filter = array()) {
193*f64dbc90SAndreas Gohr    // FIXME implement
194*f64dbc90SAndreas Gohr    //    return 0;
195*f64dbc90SAndreas Gohr    //}
196*f64dbc90SAndreas Gohr
197*f64dbc90SAndreas Gohr    /**
198*f64dbc90SAndreas Gohr     * Define a group [implement only where required/possible]
199*f64dbc90SAndreas Gohr     *
200*f64dbc90SAndreas Gohr     * Set addGroup capability when implemented
201*f64dbc90SAndreas Gohr     *
202*f64dbc90SAndreas Gohr     * @param   string $group
203*f64dbc90SAndreas Gohr     * @return  bool
204*f64dbc90SAndreas Gohr     */
205*f64dbc90SAndreas Gohr    //public function addGroup($group) {
206*f64dbc90SAndreas Gohr    // FIXME implement
207*f64dbc90SAndreas Gohr    //    return false;
208*f64dbc90SAndreas Gohr    //}
209*f64dbc90SAndreas Gohr
210*f64dbc90SAndreas Gohr    /**
211*f64dbc90SAndreas Gohr     * Retrieve groups [implement only where required/possible]
212*f64dbc90SAndreas Gohr     *
213*f64dbc90SAndreas Gohr     * Set getGroups capability when implemented
214*f64dbc90SAndreas Gohr     *
215*f64dbc90SAndreas Gohr     * @param   int $start
216*f64dbc90SAndreas Gohr     * @param   int $limit
217*f64dbc90SAndreas Gohr     * @return  array
218*f64dbc90SAndreas Gohr     */
219*f64dbc90SAndreas Gohr    //public function retrieveGroups($start = 0, $limit = 0) {
220*f64dbc90SAndreas Gohr    // FIXME implement
221*f64dbc90SAndreas Gohr    //    return array();
222*f64dbc90SAndreas Gohr    //}
223*f64dbc90SAndreas Gohr
224*f64dbc90SAndreas Gohr    /**
225*f64dbc90SAndreas Gohr     * Return case sensitivity of the backend
226*f64dbc90SAndreas Gohr     *
227*f64dbc90SAndreas Gohr     * When your backend is caseinsensitive (eg. you can login with USER and
228*f64dbc90SAndreas Gohr     * user) then you need to overwrite this method and return false
229*f64dbc90SAndreas Gohr     *
230*f64dbc90SAndreas Gohr     * @return bool
231*f64dbc90SAndreas Gohr     */
232*f64dbc90SAndreas Gohr    public function isCaseSensitive() {
233*f64dbc90SAndreas Gohr        return true;
234*f64dbc90SAndreas Gohr    }
235*f64dbc90SAndreas Gohr
236*f64dbc90SAndreas Gohr    /**
237*f64dbc90SAndreas Gohr     * Sanitize a given username
238*f64dbc90SAndreas Gohr     *
239*f64dbc90SAndreas Gohr     * This function is applied to any user name that is given to
240*f64dbc90SAndreas Gohr     * the backend and should also be applied to any user name within
241*f64dbc90SAndreas Gohr     * the backend before returning it somewhere.
242*f64dbc90SAndreas Gohr     *
243*f64dbc90SAndreas Gohr     * This should be used to enforce username restrictions.
244*f64dbc90SAndreas Gohr     *
245*f64dbc90SAndreas Gohr     * @param string $user username
246*f64dbc90SAndreas Gohr     * @return string the cleaned username
247*f64dbc90SAndreas Gohr     */
248*f64dbc90SAndreas Gohr    public function cleanUser($user) {
249*f64dbc90SAndreas Gohr        return $user;
250*f64dbc90SAndreas Gohr    }
251*f64dbc90SAndreas Gohr
252*f64dbc90SAndreas Gohr    /**
253*f64dbc90SAndreas Gohr     * Sanitize a given groupname
254*f64dbc90SAndreas Gohr     *
255*f64dbc90SAndreas Gohr     * This function is applied to any groupname that is given to
256*f64dbc90SAndreas Gohr     * the backend and should also be applied to any groupname within
257*f64dbc90SAndreas Gohr     * the backend before returning it somewhere.
258*f64dbc90SAndreas Gohr     *
259*f64dbc90SAndreas Gohr     * This should be used to enforce groupname restrictions.
260*f64dbc90SAndreas Gohr     *
261*f64dbc90SAndreas Gohr     * Groupnames are to be passed without a leading '@' here.
262*f64dbc90SAndreas Gohr     *
263*f64dbc90SAndreas Gohr     * @param  string $group groupname
264*f64dbc90SAndreas Gohr     * @return string the cleaned groupname
265*f64dbc90SAndreas Gohr     */
266*f64dbc90SAndreas Gohr    public function cleanGroup($group) {
267*f64dbc90SAndreas Gohr        return $group;
268*f64dbc90SAndreas Gohr    }
269*f64dbc90SAndreas Gohr
270*f64dbc90SAndreas Gohr    /**
271*f64dbc90SAndreas Gohr     * Check Session Cache validity [implement only where required/possible]
272*f64dbc90SAndreas Gohr     *
273*f64dbc90SAndreas Gohr     * DokuWiki caches user info in the user's session for the timespan defined
274*f64dbc90SAndreas Gohr     * in $conf['auth_security_timeout'].
275*f64dbc90SAndreas Gohr     *
276*f64dbc90SAndreas Gohr     * This makes sure slow authentication backends do not slow down DokuWiki.
277*f64dbc90SAndreas Gohr     * This also means that changes to the user database will not be reflected
278*f64dbc90SAndreas Gohr     * on currently logged in users.
279*f64dbc90SAndreas Gohr     *
280*f64dbc90SAndreas Gohr     * To accommodate for this, the user manager plugin will touch a reference
281*f64dbc90SAndreas Gohr     * file whenever a change is submitted. This function compares the filetime
282*f64dbc90SAndreas Gohr     * of this reference file with the time stored in the session.
283*f64dbc90SAndreas Gohr     *
284*f64dbc90SAndreas Gohr     * This reference file mechanism does not reflect changes done directly in
285*f64dbc90SAndreas Gohr     * the backend's database through other means than the user manager plugin.
286*f64dbc90SAndreas Gohr     *
287*f64dbc90SAndreas Gohr     * Fast backends might want to return always false, to force rechecks on
288*f64dbc90SAndreas Gohr     * each page load. Others might want to use their own checking here. If
289*f64dbc90SAndreas Gohr     * unsure, do not override.
290*f64dbc90SAndreas Gohr     *
291*f64dbc90SAndreas Gohr     * @param  string $user - The username
292*f64dbc90SAndreas Gohr     * @return bool
293*f64dbc90SAndreas Gohr     */
294*f64dbc90SAndreas Gohr    //public function useSessionCache($user) {
295*f64dbc90SAndreas Gohr    // FIXME implement
296*f64dbc90SAndreas Gohr    //}
297*f64dbc90SAndreas Gohr
298*f64dbc90SAndreas Gohr    /**
299*f64dbc90SAndreas Gohr     * Select data of a specified user
300*f64dbc90SAndreas Gohr     *
301*f64dbc90SAndreas Gohr     * @param $user
302*f64dbc90SAndreas Gohr     * @return bool|array
303*f64dbc90SAndreas Gohr     */
304*f64dbc90SAndreas Gohr    protected function _selectUser($user) {
305*f64dbc90SAndreas Gohr        $sql = $this->getConf('select-user');
306*f64dbc90SAndreas Gohr
307*f64dbc90SAndreas Gohr        try {
308*f64dbc90SAndreas Gohr            $sth = $this->pdo->prepare($sql);
309*f64dbc90SAndreas Gohr            $sth->execute(array(':user' => $user));
310*f64dbc90SAndreas Gohr            $result = $sth->fetchAll();
311*f64dbc90SAndreas Gohr            $sth->closeCursor();
312*f64dbc90SAndreas Gohr            $sth = null;
313*f64dbc90SAndreas Gohr        } catch(PDOException $e) {
314*f64dbc90SAndreas Gohr            $this->_debug($e);
315*f64dbc90SAndreas Gohr            $result = array();
316*f64dbc90SAndreas Gohr        }
317*f64dbc90SAndreas Gohr        $found = count($result);
318*f64dbc90SAndreas Gohr        if($found == 0) return false;
319*f64dbc90SAndreas Gohr
320*f64dbc90SAndreas Gohr        if($found > 1) {
321*f64dbc90SAndreas Gohr            $this->_debug('Found more than one matching user', -1, __LINE__);
322*f64dbc90SAndreas Gohr            return false;
323*f64dbc90SAndreas Gohr        }
324*f64dbc90SAndreas Gohr
325*f64dbc90SAndreas Gohr        $data = array_shift($result);
326*f64dbc90SAndreas Gohr        $dataok = true;
327*f64dbc90SAndreas Gohr
328*f64dbc90SAndreas Gohr        if(!isset($data['user'])) {
329*f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'user' attribute", -1, __LINE__);
330*f64dbc90SAndreas Gohr            $dataok = false;
331*f64dbc90SAndreas Gohr        }
332*f64dbc90SAndreas Gohr        if(!isset($data['hash']) && !isset($data['clear'])) {
333*f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'clear' or 'hash' attribute", -1, __LINE__);
334*f64dbc90SAndreas Gohr            $dataok = false;
335*f64dbc90SAndreas Gohr        }
336*f64dbc90SAndreas Gohr        if(!isset($data['name'])) {
337*f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'name' attribute", -1, __LINE__);
338*f64dbc90SAndreas Gohr            $dataok = false;
339*f64dbc90SAndreas Gohr        }
340*f64dbc90SAndreas Gohr        if(!isset($data['mail'])) {
341*f64dbc90SAndreas Gohr            $this->_debug("Statement did not return 'mail' attribute", -1, __LINE__);
342*f64dbc90SAndreas Gohr            $dataok = false;
343*f64dbc90SAndreas Gohr        }
344*f64dbc90SAndreas Gohr
345*f64dbc90SAndreas Gohr        if(!$dataok) return false;
346*f64dbc90SAndreas Gohr        return $data;
347*f64dbc90SAndreas Gohr    }
348*f64dbc90SAndreas Gohr
349*f64dbc90SAndreas Gohr    /**
350*f64dbc90SAndreas Gohr     * Wrapper around msg() but outputs only when debug is enabled
351*f64dbc90SAndreas Gohr     *
352*f64dbc90SAndreas Gohr     * @param string|Exception $message
353*f64dbc90SAndreas Gohr     * @param int $err
354*f64dbc90SAndreas Gohr     * @param int $line
355*f64dbc90SAndreas Gohr     */
356*f64dbc90SAndreas Gohr    protected function _debug($message, $err = 0, $line = 0) {
357*f64dbc90SAndreas Gohr        if(!$this->getConf('debug')) return;
358*f64dbc90SAndreas Gohr        if(is_a($message, 'Exception')) {
359*f64dbc90SAndreas Gohr            $err = -1;
360*f64dbc90SAndreas Gohr            $line = $message->getLine();
361*f64dbc90SAndreas Gohr            $msg = $message->getMessage();
362*f64dbc90SAndreas Gohr        } else {
363*f64dbc90SAndreas Gohr            $msg = $message;
364*f64dbc90SAndreas Gohr        }
365*f64dbc90SAndreas Gohr
366*f64dbc90SAndreas Gohr        if(defined('DOKU_UNITTEST')) {
367*f64dbc90SAndreas Gohr            printf("\n%s, %s:%d\n", $msg, __FILE__, $line);
368*f64dbc90SAndreas Gohr        } else {
369*f64dbc90SAndreas Gohr            msg('authpdo: ' . $msg, $err, $line, __FILE__);
370*f64dbc90SAndreas Gohr        }
371*f64dbc90SAndreas Gohr    }
372*f64dbc90SAndreas Gohr}
373*f64dbc90SAndreas Gohr
374*f64dbc90SAndreas Gohr// vim:ts=4:sw=4:et:
375