Completed
Push — sidebaracl ( 7a112d...7c3e4a )
by Andreas
04:38
created

lib/plugins/authmysql/auth.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
// must be run within Dokuwiki
3
if(!defined('DOKU_INC')) die();
4
5
/**
6
 * MySQL authentication backend
7
 *
8
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9
 * @author     Andreas Gohr <[email protected]>
10
 * @author     Chris Smith <[email protected]>
11
 * @author     Matthias Grimm <[email protected]>
12
 * @author     Jan Schumann <[email protected]>
13
 */
14
class auth_plugin_authmysql extends DokuWiki_Auth_Plugin {
15
    /** @var resource holds the database connection */
16
    protected $dbcon = 0;
17
    /** @var int database version*/
18
    protected $dbver = 0;
19
    /** @var int database revision */
20
    protected $dbrev = 0;
21
    /** @var int database subrevision */
22
    protected $dbsub = 0;
23
24
    /** @var array cache to avoid re-reading user info data */
25
    protected $cacheUserInfo = array();
26
27
    /**
28
     * Constructor
29
     *
30
     * checks if the mysql interface is available, otherwise it will
31
     * set the variable $success of the basis class to false
32
     *
33
     * @author Matthias Grimm <[email protected]>
34
     */
35
    public function __construct() {
36
        parent::__construct();
37
38
        if(!function_exists('mysql_connect')) {
39
            $this->_debug("MySQL err: PHP MySQL extension not found.", -1, __LINE__, __FILE__);
40
            $this->success = false;
41
            return;
42
        }
43
44
        // set capabilities based upon config strings set
45
        if(!$this->getConf('server') || !$this->getConf('user') || !$this->getConf('database')) {
46
            $this->_debug("MySQL err: insufficient configuration.", -1, __LINE__, __FILE__);
47
48
            $this->success = false;
49
            return;
50
        }
51
52
        $this->cando['addUser']   = $this->_chkcnf(
53
            array(
54
                 'getUserInfo',
55
                 'getGroups',
56
                 'addUser',
57
                 'getUserID',
58
                 'getGroupID',
59
                 'addGroup',
60
                 'addUserGroup'
61
            ), true
62
        );
63
        $this->cando['delUser']   = $this->_chkcnf(
64
            array(
65
                 'getUserID',
66
                 'delUser',
67
                 'delUserRefs'
68
            ), true
69
        );
70
        $this->cando['modLogin']  = $this->_chkcnf(
71
            array(
72
                 'getUserID',
73
                 'updateUser',
74
                 'UpdateTarget'
75
            ), true
76
        );
77
        $this->cando['modPass']   = $this->cando['modLogin'];
78
        $this->cando['modName']   = $this->cando['modLogin'];
79
        $this->cando['modMail']   = $this->cando['modLogin'];
80
        $this->cando['modGroups'] = $this->_chkcnf(
81
            array(
82
                 'getUserID',
83
                 'getGroups',
84
                 'getGroupID',
85
                 'addGroup',
86
                 'addUserGroup',
87
                 'delGroup',
88
                 'getGroupID',
89
                 'delUserGroup'
90
            ), true
91
        );
92
        /* getGroups is not yet supported
93
           $this->cando['getGroups']    = $this->_chkcnf(array('getGroups',
94
           'getGroupID'),false); */
95
        $this->cando['getUsers']     = $this->_chkcnf(
96
            array(
97
                 'getUsers',
98
                 'getUserInfo',
99
                 'getGroups'
100
            ), false
101
        );
102
        $this->cando['getUserCount'] = $this->_chkcnf(array('getUsers'), false);
103
104
        if($this->getConf('debug') >= 2) {
105
            $candoDebug = '';
106
            foreach($this->cando as $cd => $value) {
107
                if($value) { $value = 'yes'; } else { $value = 'no'; }
108
                $candoDebug .= $cd . ": " . $value . " | ";
109
            }
110
            $this->_debug("authmysql cando: " . $candoDebug, 0, __LINE__, __FILE__);
111
        }
112
    }
113
114
    /**
115
     * Check if the given config strings are set
116
     *
117
     * @author  Matthias Grimm <[email protected]>
118
     *
119
     * @param   string[] $keys
120
     * @param   bool  $wop is this a check for a write operation?
121
     * @return  bool
122
     */
123
    protected function _chkcnf($keys, $wop = false) {
124
        foreach($keys as $key) {
125
            if(!$this->getConf($key)) return false;
126
        }
127
128
        /* write operation and lock array filled with tables names? */
129
        if($wop && (!is_array($this->getConf('TablesToLock')) ||
130
            !count($this->getConf('TablesToLock')))
131
        ) {
132
            return false;
133
        }
134
135
        return true;
136
    }
137
138
    /**
139
     * Checks if the given user exists and the given plaintext password
140
     * is correct. Furtheron it might be checked wether the user is
141
     * member of the right group
142
     *
143
     * Depending on which SQL string is defined in the config, password
144
     * checking is done here (getpass) or by the database (passcheck)
145
     *
146
     * @param  string $user user who would like access
147
     * @param  string $pass user's clear text password to check
148
     * @return bool
149
     *
150
     * @author  Andreas Gohr <[email protected]>
151
     * @author  Matthias Grimm <[email protected]>
152
     */
153
    public function checkPass($user, $pass) {
154
        global $conf;
155
        $rc = false;
156
157
        if($this->_openDB()) {
158
            $sql    = str_replace('%{user}', $this->_escape($user), $this->getConf('checkPass'));
159
            $sql    = str_replace('%{pass}', $this->_escape($pass), $sql);
160
            $sql    = str_replace('%{dgroup}', $this->_escape($conf['defaultgroup']), $sql);
161
            $result = $this->_queryDB($sql);
162
163
            if($result !== false && count($result) == 1) {
164
                if($this->getConf('forwardClearPass') == 1) {
165
                    $rc = true;
166
                } else {
167
                    $rc = auth_verifyPassword($pass, $result[0]['pass']);
168
                }
169
            }
170
            $this->_closeDB();
171
        }
172
        return $rc;
173
    }
174
175
    /**
176
     * Return user info
177
     *
178
     * @author  Andreas Gohr <[email protected]>
179
     * @author  Matthias Grimm <[email protected]>
180
     *
181
     * @param string $user user login to get data for
182
     * @param bool $requireGroups  when true, group membership information should be included in the returned array;
183
     *                             when false, it maybe included, but is not required by the caller
184
     * @return array|bool
185
     */
186
    public function getUserData($user, $requireGroups=true) {
187
        if($this->_cacheExists($user, $requireGroups)) {
188
            return $this->cacheUserInfo[$user];
189
        }
190
191
        if($this->_openDB()) {
192
            $this->_lockTables("READ");
193
            $info = $this->_getUserInfo($user, $requireGroups);
194
            $this->_unlockTables();
195
            $this->_closeDB();
196
        } else {
197
            $info = false;
198
        }
199
        return $info;
200
    }
201
202
    /**
203
     * Create a new User. Returns false if the user already exists,
204
     * null when an error occurred and true if everything went well.
205
     *
206
     * The new user will be added to the default group by this
207
     * function if grps are not specified (default behaviour).
208
     *
209
     * @author  Andreas Gohr <[email protected]>
210
     * @author  Chris Smith <[email protected]>
211
     * @author  Matthias Grimm <[email protected]>
212
     *
213
     * @param string $user  nick of the user
214
     * @param string $pwd   clear text password
215
     * @param string $name  full name of the user
216
     * @param string $mail  email address
217
     * @param array  $grps  array of groups the user should become member of
218
     * @return bool|null
219
     */
220
    public function createUser($user, $pwd, $name, $mail, $grps = null) {
221
        global $conf;
222
223
        if($this->_openDB()) {
224
            if(($info = $this->_getUserInfo($user)) !== false) {
225
                msg($this->getLang('userexists'), -1);
226
                return false; // user already exists
227
            }
228
229
            // set defaultgroup if no groups were given
230
            if($grps == null) {
231
                $grps = array($conf['defaultgroup']);
232
            }
233
234
            $this->_lockTables("WRITE");
235
            $pwd = $this->getConf('forwardClearPass') ? $pwd : auth_cryptPassword($pwd);
236
            $rc  = $this->_addUser($user, $pwd, $name, $mail, $grps);
237
            $this->_unlockTables();
238
            $this->_closeDB();
239
            if(!$rc) {
240
                msg($this->getLang('writefail'));
241
                return null;
242
            }
243
            return true;
244
        } else {
245
            msg($this->getLang('connectfail'), -1);
246
        }
247
        return null; // return error
248
    }
249
250
    /**
251
     * Modify user data
252
     *
253
     * An existing user dataset will be modified. Changes are given in an array.
254
     *
255
     * The dataset update will be rejected if the user name should be changed
256
     * to an already existing one.
257
     *
258
     * The password must be provided unencrypted. Pasword encryption is done
259
     * automatically if configured.
260
     *
261
     * If one or more groups can't be updated, an error will be set. In
262
     * this case the dataset might already be changed and we can't rollback
263
     * the changes. Transactions would be really useful here.
264
     *
265
     * modifyUser() may be called without SQL statements defined that are
266
     * needed to change group membership (for example if only the user profile
267
     * should be modified). In this case we assure that we don't touch groups
268
     * even when $changes['grps'] is set by mistake.
269
     *
270
     * @author  Chris Smith <[email protected]>
271
     * @author  Matthias Grimm <[email protected]>
272
     *
273
     * @param   string $user    nick of the user to be changed
274
     * @param   array  $changes array of field/value pairs to be changed (password will be clear text)
275
     * @return  bool   true on success, false on error
276
     */
277
    public function modifyUser($user, $changes) {
278
        $rc = false;
279
280
        if(!is_array($changes) || !count($changes)) {
281
            return true; // nothing to change
282
        }
283
284
        if($this->_openDB()) {
285
            $this->_lockTables("WRITE");
286
287
            $rc = $this->_updateUserInfo($user, $changes);
288
289
            if(!$rc) {
290
                msg($this->getLang('usernotexists'), -1);
291
            } elseif(isset($changes['grps']) && $this->cando['modGroups']) {
292
                $groups = $this->_getGroups($user);
293
                $grpadd = array_diff($changes['grps'], $groups);
294
                $grpdel = array_diff($groups, $changes['grps']);
295
296
                foreach($grpadd as $group) {
297
                    if(($this->_addUserToGroup($user, $group, true)) == false) {
298
                        $rc = false;
299
                    }
300
                }
301
302
                foreach($grpdel as $group) {
303
                    if(($this->_delUserFromGroup($user, $group)) == false) {
304
                        $rc = false;
305
                    }
306
                }
307
308
                if(!$rc) msg($this->getLang('writefail'));
309
            }
310
311
            $this->_unlockTables();
312
            $this->_closeDB();
313
        } else {
314
            msg($this->getLang('connectfail'), -1);
315
        }
316
        return $rc;
317
    }
318
319
    /**
320
     * [public function]
321
     *
322
     * Remove one or more users from the list of registered users
323
     *
324
     * @param   array  $users   array of users to be deleted
325
     * @return  int             the number of users deleted
326
     *
327
     * @author  Christopher Smith <[email protected]>
328
     * @author  Matthias Grimm <[email protected]>
329
     */
330
    function deleteUsers($users) {
331
        $count = 0;
332
333
        if($this->_openDB()) {
334
            if(is_array($users) && count($users)) {
335
                $this->_lockTables("WRITE");
336
                foreach($users as $user) {
337
                    if($this->_delUser($user)) {
338
                        $count++;
339
                    }
340
                }
341
                $this->_unlockTables();
342
            }
343
            $this->_closeDB();
344
        } else {
345
            msg($this->getLang('connectfail'), -1);
346
        }
347
        return $count;
348
    }
349
350
    /**
351
     * Counts users which meet certain $filter criteria.
352
     *
353
     * @author  Matthias Grimm <[email protected]>
354
     *
355
     * @param  array $filter  filter criteria in item/pattern pairs
356
     * @return int count of found users
357
     */
358
    public function getUserCount($filter = array()) {
359
        $rc = 0;
360
361
        if($this->_openDB()) {
362
            $sql = $this->_createSQLFilter($this->getConf('getUsers'), $filter);
363
364
            if($this->dbver >= 4) {
365
                $sql = substr($sql, 6); /* remove 'SELECT' or 'select' */
366
                $sql = "SELECT SQL_CALC_FOUND_ROWS".$sql." LIMIT 1";
367
                $this->_queryDB($sql);
368
                $result = $this->_queryDB("SELECT FOUND_ROWS()");
369
                $rc     = $result[0]['FOUND_ROWS()'];
370
            } else if(($result = $this->_queryDB($sql)))
371
                $rc = count($result);
372
373
            $this->_closeDB();
374
        }
375
        return $rc;
376
    }
377
378
    /**
379
     * Bulk retrieval of user data
380
     *
381
     * @author  Matthias Grimm <[email protected]>
382
     *
383
     * @param  int          $first  index of first user to be returned
384
     * @param  int          $limit  max number of users to be returned
385
     * @param  array $filter array of field/pattern pairs
386
     * @return  array userinfo (refer getUserData for internal userinfo details)
387
     */
388
    public function retrieveUsers($first = 0, $limit = 0, $filter = array()) {
389
        $out = array();
390
391
        if($this->_openDB()) {
392
            $this->_lockTables("READ");
393
            $sql = $this->_createSQLFilter($this->getConf('getUsers'), $filter);
394
            $sql .= " ".$this->getConf('SortOrder');
395
            if($limit) {
396
                $sql .= " LIMIT $first, $limit";
397
            } elseif($first) {
398
                $sql .= " LIMIT $first";
399
            }
400
            $result = $this->_queryDB($sql);
401
402
            if(!empty($result)) {
403
                foreach($result as $user) {
404
                    if(($info = $this->_getUserInfo($user['user']))) {
405
                        $out[$user['user']] = $info;
406
                    }
407
                }
408
            }
409
410
            $this->_unlockTables();
411
            $this->_closeDB();
412
        }
413
        return $out;
414
    }
415
416
    /**
417
     * Give user membership of a group
418
     *
419
     * @author  Matthias Grimm <[email protected]>
420
     *
421
     * @param   string $user
422
     * @param   string $group
423
     * @return  bool   true on success, false on error
424
     */
425
    protected function joinGroup($user, $group) {
426
        $rc = false;
427
428
        if($this->_openDB()) {
429
            $this->_lockTables("WRITE");
430
            $rc = $this->_addUserToGroup($user, $group);
431
            $this->_unlockTables();
432
            $this->_closeDB();
433
        }
434
        return $rc;
435
    }
436
437
    /**
438
     * Remove user from a group
439
     *
440
     * @author  Matthias Grimm <[email protected]>
441
     *
442
     * @param   string $user  user that leaves a group
443
     * @param   string $group group to leave
444
     * @return  bool
445
     */
446
    protected function leaveGroup($user, $group) {
447
        $rc = false;
448
449
        if($this->_openDB()) {
450
            $this->_lockTables("WRITE");
451
            $rc  = $this->_delUserFromGroup($user, $group);
452
            $this->_unlockTables();
453
            $this->_closeDB();
454
        }
455
        return $rc;
456
    }
457
458
    /**
459
     * MySQL is case-insensitive
460
     */
461
    public function isCaseSensitive() {
462
        return false;
463
    }
464
465
    /**
466
     * Adds a user to a group.
467
     *
468
     * If $force is set to true non existing groups would be created.
469
     *
470
     * The database connection must already be established. Otherwise
471
     * this function does nothing and returns 'false'. It is strongly
472
     * recommended to call this function only after all participating
473
     * tables (group and usergroup) have been locked.
474
     *
475
     * @author Matthias Grimm <[email protected]>
476
     *
477
     * @param   string $user    user to add to a group
478
     * @param   string $group   name of the group
479
     * @param   bool   $force   create missing groups
480
     * @return  bool   true on success, false on error
481
     */
482
    protected function _addUserToGroup($user, $group, $force = false) {
483
        $newgroup = 0;
484
485
        if(($this->dbcon) && ($user)) {
486
            $gid = $this->_getGroupID($group);
487
            if(!$gid) {
488
                if($force) { // create missing groups
489
                    $sql      = str_replace('%{group}', $this->_escape($group), $this->getConf('addGroup'));
490
                    $gid      = $this->_modifyDB($sql);
491
                    $newgroup = 1; // group newly created
492
                }
493
                if(!$gid) return false; // group didn't exist and can't be created
494
            }
495
496
            $sql = $this->getConf('addUserGroup');
497
            if(strpos($sql, '%{uid}') !== false) {
498
                $uid = $this->_getUserID($user);
499
                $sql = str_replace('%{uid}', $this->_escape($uid), $sql);
500
            }
501
            $sql = str_replace('%{user}', $this->_escape($user), $sql);
502
            $sql = str_replace('%{gid}', $this->_escape($gid), $sql);
503
            $sql = str_replace('%{group}', $this->_escape($group), $sql);
504
            if($this->_modifyDB($sql) !== false) {
505
                $this->_flushUserInfoCache($user);
506
                return true;
507
            }
508
509
            if($newgroup) { // remove previously created group on error
510
                $sql = str_replace('%{gid}', $this->_escape($gid), $this->getConf('delGroup'));
511
                $sql = str_replace('%{group}', $this->_escape($group), $sql);
512
                $this->_modifyDB($sql);
513
            }
514
        }
515
        return false;
516
    }
517
518
    /**
519
     * Remove user from a group
520
     *
521
     * @author  Matthias Grimm <[email protected]>
522
     *
523
     * @param   string $user  user that leaves a group
524
     * @param   string $group group to leave
525
     * @return  bool   true on success, false on error
526
     */
527
    protected function _delUserFromGroup($user, $group) {
528
        $rc = false;
529
530
        if(($this->dbcon) && ($user)) {
531
            $sql = $this->getConf('delUserGroup');
532
            if(strpos($sql, '%{uid}') !== false) {
533
                $uid = $this->_getUserID($user);
534
                $sql = str_replace('%{uid}', $this->_escape($uid), $sql);
535
            }
536
            $gid = $this->_getGroupID($group);
537
            if($gid) {
538
                $sql = str_replace('%{user}', $this->_escape($user), $sql);
539
                $sql = str_replace('%{gid}', $this->_escape($gid), $sql);
540
                $sql = str_replace('%{group}', $this->_escape($group), $sql);
541
                $rc  = $this->_modifyDB($sql) == 0 ? true : false;
542
543
                if ($rc) {
544
                    $this->_flushUserInfoCache($user);
545
                }
546
            }
547
        }
548
        return $rc;
549
    }
550
551
    /**
552
     * Retrieves a list of groups the user is a member off.
553
     *
554
     * The database connection must already be established
555
     * for this function to work. Otherwise it will return
556
     * false.
557
     *
558
     * @author Matthias Grimm <[email protected]>
559
     *
560
     * @param  string $user user whose groups should be listed
561
     * @return bool|array false on error, all groups on success
562
     */
563
    protected function _getGroups($user) {
564
        $groups = array();
565
566
        if($this->dbcon) {
567
            $sql    = str_replace('%{user}', $this->_escape($user), $this->getConf('getGroups'));
568
            $result = $this->_queryDB($sql);
569
570
            if($result !== false && count($result)) {
571
                foreach($result as $row) {
572
                    $groups[] = $row['group'];
573
                }
574
            }
575
            return $groups;
576
        }
577
        return false;
578
    }
579
580
    /**
581
     * Retrieves the user id of a given user name
582
     *
583
     * The database connection must already be established
584
     * for this function to work. Otherwise it will return
585
     * false.
586
     *
587
     * @author Matthias Grimm <[email protected]>
588
     *
589
     * @param  string $user user whose id is desired
590
     * @return mixed  user id
591
     */
592
    protected function _getUserID($user) {
593
        if($this->dbcon) {
594
            $sql    = str_replace('%{user}', $this->_escape($user), $this->getConf('getUserID'));
595
            $result = $this->_queryDB($sql);
596
            return $result === false ? false : $result[0]['id'];
597
        }
598
        return false;
599
    }
600
601
    /**
602
     * Adds a new User to the database.
603
     *
604
     * The database connection must already be established
605
     * for this function to work. Otherwise it will return
606
     * false.
607
     *
608
     * @author  Andreas Gohr <[email protected]>
609
     * @author  Chris Smith <[email protected]>
610
     * @author  Matthias Grimm <[email protected]>
611
     *
612
     * @param  string $user  login of the user
613
     * @param  string $pwd   encrypted password
614
     * @param  string $name  full name of the user
615
     * @param  string $mail  email address
616
     * @param  array  $grps  array of groups the user should become member of
617
     * @return bool
618
     */
619
    protected function _addUser($user, $pwd, $name, $mail, $grps) {
620
        if($this->dbcon && is_array($grps)) {
621
            $sql = str_replace('%{user}', $this->_escape($user), $this->getConf('addUser'));
622
            $sql = str_replace('%{pass}', $this->_escape($pwd), $sql);
623
            $sql = str_replace('%{name}', $this->_escape($name), $sql);
624
            $sql = str_replace('%{email}', $this->_escape($mail), $sql);
625
            $uid = $this->_modifyDB($sql);
626
            $gid = false;
627
            $group = '';
628
629
            if($uid) {
630
                foreach($grps as $group) {
631
                    $gid = $this->_addUserToGroup($user, $group, true);
632
                    if($gid === false) break;
633
                }
634
635
                if($gid !== false){
636
                    $this->_flushUserInfoCache($user);
637
                    return true;
638
                } else {
639
                    /* remove the new user and all group relations if a group can't
640
                     * be assigned. Newly created groups will remain in the database
641
                     * and won't be removed. This might create orphaned groups but
642
                     * is not a big issue so we ignore this problem here.
643
                     */
644
                    $this->_delUser($user);
645
                    $this->_debug("MySQL err: Adding user '$user' to group '$group' failed.", -1, __LINE__, __FILE__);
646
                }
647
            }
648
        }
649
        return false;
650
    }
651
652
    /**
653
     * Deletes a given user and all his group references.
654
     *
655
     * The database connection must already be established
656
     * for this function to work. Otherwise it will return
657
     * false.
658
     *
659
     * @author Matthias Grimm <[email protected]>
660
     *
661
     * @param  string $user username of the user to be deleted
662
     * @return bool
663
     */
664
    protected function _delUser($user) {
665
        if($this->dbcon) {
666
            $uid = $this->_getUserID($user);
667
            if($uid) {
668
                $sql = str_replace('%{uid}', $this->_escape($uid), $this->getConf('delUserRefs'));
669
                $this->_modifyDB($sql);
670
                $sql = str_replace('%{uid}', $this->_escape($uid), $this->getConf('delUser'));
671
                $sql = str_replace('%{user}', $this->_escape($user), $sql);
672
                $this->_modifyDB($sql);
673
                $this->_flushUserInfoCache($user);
674
                return true;
675
            }
676
        }
677
        return false;
678
    }
679
680
    /**
681
     * Flush cached user information
682
     *
683
     * @author Christopher Smith <[email protected]>
684
     *
685
     * @param  string  $user username of the user whose data is to be removed from the cache
686
     *                       if null, empty the whole cache
687
     */
688
    protected function _flushUserInfoCache($user=null) {
689
        if (is_null($user)) {
690
            $this->cacheUserInfo = array();
691
        } else {
692
            unset($this->cacheUserInfo[$user]);
693
        }
694
    }
695
696
    /**
697
     * Quick lookup to see if a user's information has been cached
698
     *
699
     * This test does not need a database connection or read lock
700
     *
701
     * @author Christopher Smith <[email protected]>
702
     *
703
     * @param  string  $user  username to be looked up in the cache
704
     * @param  bool    $requireGroups  true, if cached info should include group memberships
705
     *
706
     * @return bool    existence of required user information in the cache
707
     */
708
    protected function _cacheExists($user, $requireGroups=true) {
709
        if (isset($this->cacheUserInfo[$user])) {
710
            if (!is_array($this->cacheUserInfo[$user])) {
711
                return true;          // user doesn't exist
712
            }
713
714
            if (!$requireGroups || isset($this->cacheUserInfo[$user]['grps'])) {
715
                return true;
716
            }
717
        }
718
719
        return false;
720
    }
721
722
    /**
723
     * Get a user's information
724
     *
725
     * The database connection must already be established for this function to work.
726
     *
727
     * @author Christopher Smith <[email protected]>
728
     *
729
     * @param  string  $user  username of the user whose information is being reterieved
730
     * @param  bool    $requireGroups  true if group memberships should be included
731
     * @param  bool    $useCache       true if ok to return cached data & to cache returned data
732
     *
733
     * @return mixed   false|array     false if the user doesn't exist
734
     *                                 array containing user information if user does exist
735
     */
736
    protected function _getUserInfo($user, $requireGroups=true, $useCache=true) {
737
        $info = null;
738
739
        if ($useCache && isset($this->cacheUserInfo[$user])) {
740
            $info = $this->cacheUserInfo[$user];
741
        }
742
743
        if (is_null($info)) {
744
            $info = $this->_retrieveUserInfo($user);
745
        }
746
747
        if (($requireGroups == true) && $info && !isset($info['grps'])) {
748
            $info['grps'] = $this->_getGroups($user);
749
        }
750
751
        if ($useCache) {
752
            $this->cacheUserInfo[$user] = $info;
753
        }
754
755
        return $info;
756
    }
757
758
    /**
759
     * retrieveUserInfo
760
     *
761
     * Gets the data for a specific user. The database connection
762
     * must already be established for this function to work.
763
     * Otherwise it will return 'false'.
764
     *
765
     * @author Matthias Grimm <[email protected]>
766
     *
767
     * @param  string $user  user's nick to get data for
768
     * @return false|array false on error, user info on success
769
     */
770
    protected function _retrieveUserInfo($user) {
771
        $sql    = str_replace('%{user}', $this->_escape($user), $this->getConf('getUserInfo'));
772
        $result = $this->_queryDB($sql);
773
        if($result !== false && count($result)) {
774
            $info         = $result[0];
775
            return $info;
776
        }
777
        return false;
778
    }
779
780
    /**
781
     * Updates the user info in the database
782
     *
783
     * Update a user data structure in the database according changes
784
     * given in an array. The user name can only be changes if it didn't
785
     * exists already. If the new user name exists the update procedure
786
     * will be aborted. The database keeps unchanged.
787
     *
788
     * The database connection has already to be established for this
789
     * function to work. Otherwise it will return 'false'.
790
     *
791
     * The password will be encrypted if necessary.
792
     *
793
     * @param  string $user    user's nick being updated
794
     * @param  array $changes  array of items to change as pairs of item and value
795
     * @return bool true on success or false on error
796
     *
797
     * @author Matthias Grimm <[email protected]>
798
     */
799
    protected function _updateUserInfo($user, $changes) {
800
        $sql = $this->getConf('updateUser')." ";
801
        $cnt = 0;
802
        $err = 0;
803
804
        if($this->dbcon) {
805
            $uid = $this->_getUserID($user);
806
            if ($uid === false) {
807
                return false;
808
            }
809
810
            foreach($changes as $item => $value) {
811
                if($item == 'user') {
812
                    if(($this->_getUserID($changes['user']))) {
813
                        $err = 1; /* new username already exists */
814
                        break; /* abort update */
815
                    }
816
                    if($cnt++ > 0) $sql .= ", ";
817
                    $sql .= str_replace('%{user}', $value, $this->getConf('UpdateLogin'));
818
                } else if($item == 'name') {
819
                    if($cnt++ > 0) $sql .= ", ";
820
                    $sql .= str_replace('%{name}', $value, $this->getConf('UpdateName'));
821
                } else if($item == 'pass') {
822
                    if(!$this->getConf('forwardClearPass'))
823
                        $value = auth_cryptPassword($value);
824
                    if($cnt++ > 0) $sql .= ", ";
825
                    $sql .= str_replace('%{pass}', $value, $this->getConf('UpdatePass'));
826
                } else if($item == 'mail') {
827
                    if($cnt++ > 0) $sql .= ", ";
828
                    $sql .= str_replace('%{email}', $value, $this->getConf('UpdateEmail'));
829
                }
830
            }
831
832
            if($err == 0) {
833
                if($cnt > 0) {
834
                    $sql .= " ".str_replace('%{uid}', $uid, $this->getConf('UpdateTarget'));
835
                    if(get_class($this) == 'auth_mysql') $sql .= " LIMIT 1"; //some PgSQL inheritance comp.
836
                    $this->_modifyDB($sql);
837
                    $this->_flushUserInfoCache($user);
838
                }
839
                return true;
840
            }
841
        }
842
        return false;
843
    }
844
845
    /**
846
     * Retrieves the group id of a given group name
847
     *
848
     * The database connection must already be established
849
     * for this function to work. Otherwise it will return
850
     * false.
851
     *
852
     * @author Matthias Grimm <[email protected]>
853
     *
854
     * @param  string $group   group name which id is desired
855
     * @return false|string group id
856
     */
857
    protected function _getGroupID($group) {
858
        if($this->dbcon) {
859
            $sql    = str_replace('%{group}', $this->_escape($group), $this->getConf('getGroupID'));
860
            $result = $this->_queryDB($sql);
861
            return $result === false ? false : $result[0]['id'];
862
        }
863
        return false;
864
    }
865
866
    /**
867
     * Opens a connection to a database and saves the handle for further
868
     * usage in the object. The successful call to this functions is
869
     * essential for most functions in this object.
870
     *
871
     * @author Matthias Grimm <[email protected]>
872
     *
873
     * @return bool
874
     */
875
    protected function _openDB() {
876
        if(!$this->dbcon) {
877
            $con = @mysql_connect($this->getConf('server'), $this->getConf('user'), $this->getConf('password'));
878
            if($con) {
879
                if((mysql_select_db($this->getConf('database'), $con))) {
880
                    if((preg_match('/^(\d+)\.(\d+)\.(\d+).*/', mysql_get_server_info($con), $result)) == 1) {
881
                        $this->dbver = $result[1];
882
                        $this->dbrev = $result[2];
883
                        $this->dbsub = $result[3];
884
                    }
885
                    $this->dbcon = $con;
886
                    if($this->getConf('charset')) {
887
                        mysql_query('SET CHARACTER SET "'.$this->getConf('charset').'"', $con);
888
                    }
889
                    return true; // connection and database successfully opened
890
                } else {
891
                    mysql_close($con);
892
                    $this->_debug("MySQL err: No access to database {$this->getConf('database')}.", -1, __LINE__, __FILE__);
893
                }
894
            } else {
895
                $this->_debug(
896
                    "MySQL err: Connection to {$this->getConf('user')}@{$this->getConf('server')} not possible.",
897
                    -1, __LINE__, __FILE__
898
                );
899
            }
900
901
            return false; // connection failed
902
        }
903
        return true; // connection already open
904
    }
905
906
    /**
907
     * Closes a database connection.
908
     *
909
     * @author Matthias Grimm <[email protected]>
910
     */
911
    protected function _closeDB() {
912
        if($this->dbcon) {
913
            mysql_close($this->dbcon);
914
            $this->dbcon = 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like 0 of type integer is incompatible with the declared type resource of property $dbcon.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
915
        }
916
    }
917
918
    /**
919
     * Sends a SQL query to the database and transforms the result into
920
     * an associative array.
921
     *
922
     * This function is only able to handle queries that returns a
923
     * table such as SELECT.
924
     *
925
     * @author Matthias Grimm <[email protected]>
926
     *
927
     * @param string $query  SQL string that contains the query
928
     * @return array|false with the result table
929
     */
930
    protected function _queryDB($query) {
931
        if($this->getConf('debug') >= 2) {
932
            msg('MySQL query: '.hsc($query), 0, __LINE__, __FILE__);
933
        }
934
935
        $resultarray = array();
936
        if($this->dbcon) {
937
            $result = @mysql_query($query, $this->dbcon);
938
            if($result) {
939
                while(($t = mysql_fetch_assoc($result)) !== false)
940
                    $resultarray[] = $t;
941
                mysql_free_result($result);
942
                return $resultarray;
943
            }
944
            $this->_debug('MySQL err: '.mysql_error($this->dbcon), -1, __LINE__, __FILE__);
945
        }
946
        return false;
947
    }
948
949
    /**
950
     * Sends a SQL query to the database
951
     *
952
     * This function is only able to handle queries that returns
953
     * either nothing or an id value such as INPUT, DELETE, UPDATE, etc.
954
     *
955
     * @author Matthias Grimm <[email protected]>
956
     *
957
     * @param string $query  SQL string that contains the query
958
     * @return int|bool insert id or 0, false on error
959
     */
960
    protected function _modifyDB($query) {
961
        if($this->getConf('debug') >= 2) {
962
            msg('MySQL query: '.hsc($query), 0, __LINE__, __FILE__);
963
        }
964
965
        if($this->dbcon) {
966
            $result = @mysql_query($query, $this->dbcon);
967
            if($result) {
968
                $rc = mysql_insert_id($this->dbcon); //give back ID on insert
969
                if($rc !== false) return $rc;
970
            }
971
            $this->_debug('MySQL err: '.mysql_error($this->dbcon), -1, __LINE__, __FILE__);
972
        }
973
        return false;
974
    }
975
976
    /**
977
     * Locked a list of tables for exclusive access so that modifications
978
     * to the database can't be disturbed by other threads. The list
979
     * could be set with $conf['plugin']['authmysql']['TablesToLock'] = array()
980
     *
981
     * If aliases for tables are used in SQL statements, also this aliases
982
     * must be locked. For eg. you use a table 'user' and the alias 'u' in
983
     * some sql queries, the array must looks like this (order is important):
984
     *   array("user", "user AS u");
985
     *
986
     * MySQL V3 is not able to handle transactions with COMMIT/ROLLBACK
987
     * so that this functionality is simulated by this function. Nevertheless
988
     * it is not as powerful as transactions, it is a good compromise in safty.
989
     *
990
     * @author Matthias Grimm <[email protected]>
991
     *
992
     * @param string $mode  could be 'READ' or 'WRITE'
993
     * @return bool
994
     */
995
    protected function _lockTables($mode) {
996
        if($this->dbcon) {
997
            $ttl = $this->getConf('TablesToLock');
998
            if(is_array($ttl) && !empty($ttl)) {
999
                if($mode == "READ" || $mode == "WRITE") {
1000
                    $sql = "LOCK TABLES ";
1001
                    $cnt = 0;
1002
                    foreach($ttl as $table) {
1003
                        if($cnt++ != 0) $sql .= ", ";
1004
                        $sql .= "$table $mode";
1005
                    }
1006
                    $this->_modifyDB($sql);
1007
                    return true;
1008
                }
1009
            }
1010
        }
1011
        return false;
1012
    }
1013
1014
    /**
1015
     * Unlock locked tables. All existing locks of this thread will be
1016
     * abrogated.
1017
     *
1018
     * @author Matthias Grimm <[email protected]>
1019
     *
1020
     * @return bool
1021
     */
1022
    protected function _unlockTables() {
1023
        if($this->dbcon) {
1024
            $this->_modifyDB("UNLOCK TABLES");
1025
            return true;
1026
        }
1027
        return false;
1028
    }
1029
1030
    /**
1031
     * Transforms the filter settings in an filter string for a SQL database
1032
     * The database connection must already be established, otherwise the
1033
     * original SQL string without filter criteria will be returned.
1034
     *
1035
     * @author Matthias Grimm <[email protected]>
1036
     *
1037
     * @param  string $sql     SQL string to which the $filter criteria should be added
1038
     * @param  array $filter  array of filter criteria as pairs of item and pattern
1039
     * @return string SQL string with attached $filter criteria on success, original SQL string on error
1040
     */
1041
    protected function _createSQLFilter($sql, $filter) {
1042
        $SQLfilter = "";
1043
        $cnt       = 0;
1044
1045
        if($this->dbcon) {
1046
            foreach($filter as $item => $pattern) {
1047
                $tmp = '%'.$this->_escape($pattern).'%';
1048
                if($item == 'user') {
1049
                    if($cnt++ > 0) $SQLfilter .= " AND ";
1050
                    $SQLfilter .= str_replace('%{user}', $tmp, $this->getConf('FilterLogin'));
1051
                } else if($item == 'name') {
1052
                    if($cnt++ > 0) $SQLfilter .= " AND ";
1053
                    $SQLfilter .= str_replace('%{name}', $tmp, $this->getConf('FilterName'));
1054
                } else if($item == 'mail') {
1055
                    if($cnt++ > 0) $SQLfilter .= " AND ";
1056
                    $SQLfilter .= str_replace('%{email}', $tmp, $this->getConf('FilterEmail'));
1057
                } else if($item == 'grps') {
1058
                    if($cnt++ > 0) $SQLfilter .= " AND ";
1059
                    $SQLfilter .= str_replace('%{group}', $tmp, $this->getConf('FilterGroup'));
1060
                }
1061
            }
1062
1063
            // we have to check SQLfilter here and must not use $cnt because if
1064
            // any of cnf['Filter????'] is not defined, a malformed SQL string
1065
            // would be generated.
1066
1067
            if(strlen($SQLfilter)) {
1068
                $glue = strpos(strtolower($sql), "where") ? " AND " : " WHERE ";
1069
                $sql  = $sql.$glue.$SQLfilter;
1070
            }
1071
        }
1072
1073
        return $sql;
1074
    }
1075
1076
    /**
1077
     * Escape a string for insertion into the database
1078
     *
1079
     * @author Andreas Gohr <[email protected]>
1080
     *
1081
     * @param  string  $string The string to escape
1082
     * @param  boolean $like   Escape wildcard chars as well?
1083
     * @return string
1084
     */
1085
    protected function _escape($string, $like = false) {
1086
        if($this->dbcon) {
1087
            $string = mysql_real_escape_string($string, $this->dbcon);
1088
        } else {
1089
            $string = addslashes($string);
1090
        }
1091
        if($like) {
1092
            $string = addcslashes($string, '%_');
1093
        }
1094
        return $string;
1095
    }
1096
1097
    /**
1098
     * Wrapper around msg() but outputs only when debug is enabled
1099
     *
1100
     * @param string $message
1101
     * @param int    $err
1102
     * @param int    $line
1103
     * @param string $file
1104
     * @return void
1105
     */
1106
    protected function _debug($message, $err, $line, $file) {
1107
        if(!$this->getConf('debug')) return;
1108
        msg($message, $err, $line, $file);
1109
    }
1110
}
1111