Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
 
4
/**
5
 * File::Passwd::Custom
6
 *
7
 * PHP versions 4 and 5
8
 *
9
 * LICENSE: This source file is subject to version 3.0 of the PHP license
10
 * that is available through the world-wide-web at the following URI:
11
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
12
 * the PHP License and are unable to obtain it through the web, please
13
 * send a note to license@php.net so we can mail you a copy immediately.
14
 *
15
 * @category   FileFormats
16
 * @package    File_Passwd
17
 * @author     Michael Wallner <mike@php.net>
18
 * @copyright  2003-2005 Michael Wallner
19
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
20
 * @version    CVS: $Id: Custom.php,v 1.10 2005/03/30 18:33:33 mike Exp $
21
 * @link       http://pear.php.net/package/File_Passwd
22
 */
23
 
24
/**
25
* Requires File::Passwd::Common
26
*/
27
require_once 'File/Passwd/Common.php';
28
 
29
/**
30
* Manipulate custom formatted passwd files
31
*
32
* Usage Example:
33
* <code>
34
* $cust = &File_Passwd::factory('Custom');
35
* $cust->setDelim('|');
36
* $cust->load();
37
* $cust->setEncFunc(array('File_Passwd', 'crypt_apr_md5'));
38
* $cust->addUser('mike', 'pass');
39
* $cust->save();
40
* </code>
41
*
42
* @author   Michael Wallner <mike@php.net>
43
* @version  $Revision: 1.10 $
44
* @access   public
45
*/
46
class File_Passwd_Custom extends File_Passwd_Common
47
{
48
    /**
49
    * Delimiter
50
    *
51
    * @access   private
52
    * @var      string
53
    */
54
    var $_delim = ':';
55
 
56
    /**
57
    * Encryption function
58
    *
59
    * @access   private
60
    * @var      string
61
    */
62
    var $_enc = array('File_Passwd', 'crypt_md5');
63
 
64
    /**
65
    * 'name map'
66
    *
67
    * @access   private
68
    * @var      array
69
    */
70
    var $_map = array();
71
 
72
    /**
73
    * Whether to use the 'name map' or not
74
    *
75
    * @var      boolean
76
    * @access   private
77
    */
78
    var $_usemap = false;
79
 
80
    /**
81
    * Constructor
82
    *
83
    * @access   protected
84
    * @return   object
85
    */
86
    function File_Passwd_Custom($file = 'passwd')
87
    {
88
        $this->__construct($file);
89
    }
90
 
91
    /**
92
    * Fast authentication of a certain user
93
    *
94
    * Returns a PEAR_Error if:
95
    *   o file doesn't exist
96
    *   o file couldn't be opened in read mode
97
    *   o file couldn't be locked exclusively
98
    *   o file couldn't be unlocked (only if auth fails)
99
    *   o file couldn't be closed (only if auth fails)
100
    *   o invalid encryption function <var>$opts[0]</var>,
101
    *     or no delimiter character <var>$opts[1]</var> was provided
102
    *
103
    * @throws   PEAR_Error  FILE_PASSWD_E_UNDEFINED |
104
    *                       FILE_PASSWD_E_FILE_NOT_OPENED |
105
    *                       FILE_PASSWD_E_FILE_NOT_LOCKED |
106
    *                       FILE_PASSWD_E_FILE_NOT_UNLOCKED |
107
    *                       FILE_PASSWD_E_FILE_NOT_CLOSED |
108
    *                       FILE_PASSWD_E_INVALID_ENC_MODE
109
    * @static   call this method statically for a reasonable fast authentication
110
    * @access   public
111
    * @return   mixed   Returns &true; if authenticated, &false; if not or
112
    *                   <classname>PEAR_Error</classname> on failure.
113
    * @param    string  $file   path to passwd file
114
    * @param    string  $user   user to authenticate
115
    * @param    string  $pass   plaintext password
116
    * @param    array   $otps   encryption function and delimiter charachter
117
    *                           (in this order)
118
    */
119
    function staticAuth($file, $user, $pass, $opts)
120
    {
121
        setType($opts, 'array');
122
        if (count($opts) != 2 || empty($opts[1])) {
123
            return PEAR::raiseError('Insufficient options.', 0);
124
        }
125
 
126
        $line = File_Passwd_Common::_auth($file, $user, $opts[1]);
127
 
128
        if (!$line || PEAR::isError($line)) {
129
            return $line;
130
        }
131
 
132
        list(,$real)= explode($opts[1], $line);
133
        $crypted    = File_Passwd_Custom::_genPass($pass, $real, $opts[0]);
134
 
135
        if (PEAR::isError($crypted)) {
136
            return $crypted;
137
        }
138
 
139
        return ($crypted === $real);
140
    }
141
 
142
    /**
143
    * Set delimiter
144
    *
145
    * You can set a custom char to delimit the columns of a data set.
146
    * Defaults to a colon (':'). Be aware that this char mustn't be
147
    * in the values of your data sets.
148
    *
149
    * @access   public
150
    * @return   void
151
    * @param    string  $delim  custom delimiting character
152
    */
153
    function setDelim($delim = ':')
154
    {
155
        @setType($delim, 'string');
156
        if (empty($delim)) {
157
            $this->_delim = ':';
158
        } else {
159
            $this->_delim = $delim{0};
160
        }
161
    }
162
 
163
    /**
164
    * Get custom delimiter
165
    *
166
    * @access   public
167
    * @return   string
168
    */
169
    function getDelim()
170
    {
171
        return $this->_delim;
172
    }
173
 
174
    /**
175
    * Set encryption function
176
    *
177
    * You can set a custom encryption function to use.
178
    * The supplied function will be called by php's call_user_function(),
179
    * so you can supply an array with a method of a class/object, too
180
    * (i.e. array('File_Passwd', 'crypt_apr_md5').
181
    *
182
    *
183
    * @throws   PEAR_Error          FILE_PASSWD_E_INVALID_ENC_MODE
184
    * @access   public
185
    * @return   mixed   Returns &true; on success or
186
    *                   <classname>PEAR_Error</classname> on failure.
187
    * @param    mixed   $function    callable encryption function
188
    */
189
    function setEncFunc($function = array('File_Passwd', 'crypt_md5'))
190
    {
191
        if (!is_callable($function)) {
192
            if (is_array($function)) {
193
                $function = implode('::', $function);
194
            }
195
            return PEAR::raiseError(
196
                sprintf(FILE_PASSWD_E_INVALID_ENC_MODE_STR, $function),
197
                FILE_PASSWD_E_INVALID_ENC_MODE
198
            );
199
        }
200
 
201
        $this->_enc = $function;
202
        return true;
203
    }
204
 
205
    /**
206
    * Get current custom encryption method
207
    *
208
    * Possible return values (examples):
209
    *   o 'md5'
210
    *   o 'File_Passwd::crypt_md5'
211
    *
212
    * @access   public
213
    * @return   string
214
    */
215
    function getEncFunc()
216
    {
217
        if (is_array($this->_enc)) {
218
            return implode('::', $this->_enc);
219
        }
220
        return $this->_enc;
221
    }
222
 
223
    /**
224
    * Whether to use the 'name map' of the extra properties or not
225
    *
226
    * @see      File_Passwd_Custom::useMap()
227
    * @see      setMap()
228
    * @see      getMap()
229
    *
230
    * @access   public
231
    * @return   boolean always true if you set a value (true/false) OR
232
    *                   the actual value if called without param
233
    *
234
    * @param    boolean $bool   whether to use the 'name map' or not
235
    */
236
    function useMap($bool = null)
237
    {
238
        if (is_null($bool)) {
239
            return $this->_usemap;
240
        }
241
        $this->_usemap = (bool) $bool;
242
        return true;
243
    }
244
 
245
    /**
246
    * Set the 'name map' to use with the extra properties of the user
247
    *
248
    * This map is used for naming the associative array of the extra properties.
249
    *
250
    * Returns a PEAR_Error if <var>$map</var> was not of type array.
251
    *
252
    * @see      getMap()
253
    * @see      useMap()
254
    *
255
    * @throws   PEAR_Error  FILE_PASSWD_E_PARAM_MUST_BE_ARRAY
256
    * @access   public
257
    * @return   mixed       true on success or PEAR_Error
258
    */
259
    function setMap($map = array())
260
    {
261
        if (!is_array($map)) {
262
            return PEAR::raiseError(
263
                sprintf(FILE_PASSWD_E_PARAM_MUST_BE_ARRAY_STR, '$map'),
264
                FILE_PASSWD_E_PARAM_MUST_BE_ARRAY
265
            );
266
        }
267
        $this->_map = $map;
268
        return true;
269
    }
270
 
271
    /**
272
    * Get the 'name map' which is used for the extra properties of the user
273
    *
274
    * @see      setMap()
275
    * @see      useMap()
276
    *
277
    * @access   public
278
    * @return   array
279
    */
280
    function getMap()
281
    {
282
        return $this->_map;
283
    }
284
 
285
    /**
286
    * Apply changes an rewrite passwd file
287
    *
288
    * Returns a PEAR_Error if:
289
    *   o directory in which the file should reside couldn't be created
290
    *   o file couldn't be opened in write mode
291
    *   o file couldn't be locked exclusively
292
    *   o file couldn't be unlocked
293
    *   o file couldn't be closed
294
    *
295
    * @throws   PEAR_Error  FILE_PASSWD_E_FILE_NOT_OPENED |
296
    *                       FILE_PASSWD_E_FILE_NOT_LOCKED |
297
    *                       FILE_PASSWD_E_FILE_NOT_UNLOCKED |
298
    *                       FILE_PASSWD_E_FILE_NOT_CLOSED
299
    * @access   public
300
    * @return   mixed   Returns &true; on success or
301
    *                   <classname>PEAR_Error</classname> on failure.
302
    */
303
    function save()
304
    {
305
        $content = '';
306
        foreach ($this->_users as $user => $array){
307
            $pass   = array_shift($array);
308
            $extra  = implode($this->_delim, $array);
309
            $content .= $user . $this->_delim . $pass;
310
            if (!empty($extra)) {
311
                $content .= $this->_delim . $extra;
312
            }
313
            $content .= "\n";
314
        }
315
        return $this->_save($content);
316
    }
317
 
318
    /**
319
    * Parse the Custom password file
320
    *
321
    * Returns a PEAR_Error if passwd file has invalid format.
322
    *
323
    * @throws   PEAR_Error  FILE_PASSWD_E_INVALID_FORMAT
324
    * @access   public
325
    * @return   mixed   Returns &true; on success or
326
    *                   <classname>PEAR_Error</classname> on failure.
327
    */
328
    function parse()
329
    {
330
        $this->_users = array();
331
        foreach ($this->_contents as $line){
332
            $parts = explode($this->_delim, $line);
333
            if (count($parts) < 2) {
334
                return PEAR::raiseError(
335
                    FILE_PASSWD_E_INVALID_FORMAT_STR,
336
                    FILE_PASSWD_E_INVALID_FORMAT
337
                );
338
            }
339
            $user = array_shift($parts);
340
            $pass = array_shift($parts);
341
            $values = array();
342
            if ($this->_usemap) {
343
                $values['pass'] = $pass;
344
                foreach ($parts as $i => $value){
345
                    if (isset($this->_map[$i])) {
346
                        $values[$this->_map[$i]] = $value;
347
                    } else {
348
                        $values[$i+1] = $value;
349
                    }
350
                }
351
            } else {
352
                $values = array_merge(array($pass), $parts);
353
            }
354
            $this->_users[$user] = $values;
355
 
356
        }
357
        $this->_contents = array();
358
        return true;
359
    }
360
 
361
    /**
362
    * Add an user
363
    *
364
    * The username must start with an alphabetical character and must NOT
365
    * contain any other characters than alphanumerics, the underline and dash.
366
    *
367
    * If you use the 'name map' you should also use these naming in
368
    * the supplied extra array, because your values would get mixed up
369
    * if they are in the wrong order, which is always true if you
370
    * DON'T use the 'name map'!
371
    *
372
    * So be warned and USE the 'name map'!
373
    *
374
    * Returns a PEAR_Error if:
375
    *   o user already exists
376
    *   o user contains illegal characters
377
    *   o encryption mode is not supported
378
    *   o any element of the <var>$extra</var> array contains the delimiter char
379
    *
380
    * @throws   PEAR_Error  FILE_PASSWD_E_EXISTS_ALREADY |
381
    *                       FILE_PASSWD_E_INVALID_ENC_MODE |
382
    *                       FILE_PASSWD_E_INVALID_CHARS
383
    * @access   public
384
    * @return   mixed   Returns &true; on success or
385
    *                   <classname>PEAR_Error</classname> on failure.
386
    * @param    string  $user   the name of the user to add
387
    * @param    string  $pass   the password of the user to add
388
    * @param    array   $extra  extra properties of user to add
389
    */
390
    function addUser($user, $pass, $extra = array())
391
    {
392
        if ($this->userExists($user)) {
393
            return PEAR::raiseError(
394
                sprintf(FILE_PASSWD_E_EXISTS_ALREADY_STR, 'User ', $user),
395
                FILE_PASSWD_E_EXISTS_ALREADY
396
            );
397
        }
398
        if (!preg_match($this->_pcre, $user) || strstr($user, $this->_delim)) {
399
            return PEAR::raiseError(
400
                sprintf(FILE_PASSWD_E_INVALID_CHARS_STR, 'User ', $user),
401
                FILE_PASSWD_E_INVALID_CHARS
402
            );
403
        }
404
        if (!is_array($extra)) {
405
            setType($extra, 'array');
406
        }
407
        foreach ($extra as $e){
408
            if (strstr($e, $this->_delim)) {
409
                return PEAR::raiseError(
410
                    sprintf(FILE_PASSWD_E_INVALID_CHARS_STR, 'Property ', $e),
411
                    FILE_PASSWD_E_INVALID_CHARS
412
                );
413
            }
414
        }
415
 
416
        $pass = $this->_genPass($pass);
417
        if (PEAR::isError($pass)) {
418
            return $pass;
419
        }
420
 
421
        /**
422
        * If you don't use the 'name map' the user array will be numeric.
423
        */
424
        if (!$this->_usemap) {
425
            array_unshift($extra, $pass);
426
            $this->_users[$user] = $extra;
427
        } else {
428
            $map = $this->_map;
429
            array_unshift($map, 'pass');
430
            $extra['pass'] = $pass;
431
            foreach ($map as $key){
432
                $this->_users[$user][$key] = @$extra[$key];
433
            }
434
        }
435
 
436
        return true;
437
    }
438
 
439
    /**
440
    * Modify properties of a certain user
441
    *
442
    * # DON'T MODIFY THE PASSWORD WITH THIS METHOD!
443
    *
444
    * You should use this method only if the 'name map' is used, too.
445
    *
446
    * Returns a PEAR_Error if:
447
    *   o user doesn't exist
448
    *   o any property contains the custom delimiter character
449
    *
450
    * @see      changePasswd()
451
    *
452
    * @throws   PEAR_Error  FILE_PASSWD_E_EXISTS_NOT |
453
    *                       FILE_PASSWD_E_INVALID_CHARS
454
    * @access   public
455
    * @return   mixed       true on success or PEAR_Error
456
    * @param    string      $user           the user to modify
457
    * @param    array       $properties     an associative array of
458
    *                                       properties to modify
459
    */
460
    function modUser($user, $properties = array())
461
    {
462
        if (!$this->userExists($user)) {
463
            return PEAR::raiseError(
464
                sprintf(FILE_PASSWD_E_EXISTS_NOT_STR, 'User ', $user),
465
                FILE_PASSWD_E_EXISTS_NOT
466
            );
467
        }
468
 
469
        if (!is_array($properties)) {
470
            setType($properties, 'array');
471
        }
472
 
473
        foreach ($properties as $key => $value){
474
            if (strstr($value, $this->_delim)) {
475
                return PEAR::raiseError(
476
                    sprintf(FILE_PASSWD_E_INVALID_CHARS_STR, 'User ', $user),
477
                    FILE_PASSWD_E_INVALID_CHARS
478
                );
479
            }
480
            $this->_users[$user][$key] = $value;
481
        }
482
 
483
        return true;
484
    }
485
 
486
    /**
487
    * Change the password of a certain user
488
    *
489
    * Returns a PEAR_Error if:
490
    *   o user doesn't exists
491
    *   o encryption mode is not supported
492
    *
493
    * @throws   PEAR_Error  FILE_PASSWD_E_EXISTS_NOT |
494
    *                       FILE_PASSWD_E_INVALID_ENC_MODE
495
    * @access   public
496
    * @return   mixed   Returns &true; on success or
497
    *                   <classname>PEAR_Error</classname> on failure.
498
    * @param    string  $user   the user whose password should be changed
499
    * @param    string  $pass   the new plaintext password
500
    */
501
    function changePasswd($user, $pass)
502
    {
503
        if (!$this->userExists($user)) {
504
            return PEAR::raiseError(
505
                sprintf(FILE_PASSWD_E_EXISTS_NOT_STR, 'User ', $user),
506
                FILE_PASSWD_E_EXISTS_NOT
507
            );
508
        }
509
 
510
        $pass = $this->_genPass($pass);
511
        if (PEAR::isError($pass)) {
512
            return $pass;
513
        }
514
 
515
        if ($this->_usemap) {
516
            $this->_users[$user]['pass'] = $pass;
517
        } else {
518
            $this->_users[$user][0] = $pass;
519
        }
520
 
521
        return true;
522
    }
523
 
524
    /**
525
    * Verify the password of a certain user
526
    *
527
    * Returns a PEAR_Error if:
528
    *   o user doesn't exist
529
    *   o encryption mode is not supported
530
    *
531
    * @throws   PEAR_Error  FILE_PASSWD_E_EXISTS_NOT |
532
    *                       FILE_PASSWD_E_INVALID_ENC_MODE
533
    * @access   public
534
    * @return   mixed   Returns &true; if passwors equal, &false; if they don't
535
    *                   or <classname>PEAR_Error</classname> on fialure.
536
    * @param    string  $user   the user whose password should be verified
537
    * @param    string  $pass   the password to verify
538
    */
539
    function verifyPasswd($user, $pass)
540
    {
541
        if (!$this->userExists($user)) {
542
            return PEAR::raiseError(
543
                sprintf(FILE_PASSWD_E_EXISTS_NOT_STR, 'User ', $user),
544
                FILE_PASSWD_E_EXISTS_NOT
545
            );
546
        }
547
        $real =
548
            $this->_usemap ?
549
            $this->_users[$user]['pass'] :
550
            $this->_users[$user][0]
551
        ;
552
        return ($real === $this->_genPass($pass, $real));
553
    }
554
 
555
    /**
556
    * Generate crypted password from the plaintext password
557
    *
558
    * Returns a PEAR_Error if actual encryption mode is not supported.
559
    *
560
    * @throws   PEAR_Error  FILE_PASSWD_E_INVALID_ENC_MODE
561
    * @access   private
562
    * @return   mixed   Returns the crypted password or
563
    *                   <classname>PEAR_Error</classname>
564
    * @param    string  $pass   the plaintext password
565
    * @param    string  $salt   the crypted password from which to gain the salt
566
    * @param    string  $func   the encryption function to use
567
    */
568
    function _genPass($pass, $salt = null, $func = null)
569
    {
570
        if (is_null($func)) {
571
            $func = $this->_enc;
572
        }
573
 
574
        if (!is_callable($func)) {
575
            if (is_array($func)) {
576
                $func = implode('::', $func);
577
            }
578
            return PEAR::raiseError(
579
                sprintf(FILE_PASSWD_E_INVALID_ENC_MODE_STR, $func),
580
                FILE_PASSWD_E_INVALID_ENC_MODE
581
            );
582
        }
583
 
584
        $return = @call_user_func($func, $pass, $salt);
585
 
586
        if (is_null($return) || $return === false) {
587
            $return = @call_user_func($func, $pass);
588
        }
589
 
590
        return $return;
591
    }
592
}
593
?>