1<?php
2
3define('SHA1_RESULTLEN', (160/8));
4define('SHA256_RESULTLEN', (256 / 8));
5define('CRAM_MD5_CONTEXTLEN', 32);
6define('MD5_RESULTLEN', (128/8));
7define('MD4_RESULTLEN', (128/8));
8define('LM_HASH_SIZE', 16);
9define('NTLMSSP_HASH_SIZE', 16);
10
11
12class DovecotCrypt extends Crypt
13{
14    private $errormsg = [];
15
16    private $salt_chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
17
18
19    /**
20     * Array
21     * Crypt type and which function handles it.
22     * array('alogrithm' => array('encoding', 'length', 'verify', 'function'))
23     */
24    public $password_schemes = array(
25        'CRYPT'      => array('NONE', 0, 'crypt_verify', 'crypt_generate'),
26        'MD5'        => array('NONE', 0, 'md5_verify', 'md5_generate'),
27        //'MD5-CRYPT'  => array('NONE', 0, 'md5_crypt_verify', 'md5_crypt_generate'),
28        'SHA'        => array('BASE64', SHA1_RESULTLEN, null, 'sha1_generate'),
29        'SHA1'       => array('BASE64', SHA1_RESULTLEN, null, 'sha1_generate'),
30        //'SHA256'     => array('BASE64', SHA256_RESULTLEN, NULL, 'sha256_generate'),
31        //'SMD5'       => array('BASE64', 0, 'smd5_verify', 'smd5_generate'),
32        //'SSHA'       => array('BASE64', 0, 'ssha_verify', 'ssha_generate'),
33        //'SSHA256'    => array('BASE64', 0, 'ssha356_verify', 'ssha256_generate'),
34        'PLAIN'      => array('NONE', 0, null, 'plain_generate'),
35        'CLEARTEXT'  => array('NONE', 0, null, 'plain_generate'),
36        'CRAM-MD5'   => array('HEX', CRAM_MD5_CONTEXTLEN, null, 'cram_md5_generate'),
37        //'HMAC-MD5'   => array('HEX', CRAM_MD5_CONTEXTLEN, NULL, 'cram_md5_generate'),
38        //'DIGEST-MD5' => array('HEX', MD5_RESULTLEN, NULL, 'digest_md5_generate'),
39        //'PLAIN-MD4'  => array('HEX', MD4_RESULTLEN, NULL, 'plain_md4_generate'),
40        //'PLAIN-MD5'  => array('HEX', MD5_RESULTLEN, NULL, 'plain_md5_generate'),
41        //'LDAP-MD5'   => array('BASE64', MD5_RESULTLEN, NULL, 'plain_md5_generate'),
42        //'LANMAN'     => array('HEX', LM_HASH_SIZE, NULL, 'lm_generate'),
43        //'NTLM'       => array('HEX', NTLMSSP_HASH_SIZE, NULL, 'ntlm_generate'),
44        //'OTP'        => array('NONE', 0, 'otp_verify', 'otp_generate'),
45        //'SKEY'       => array('NONE', 0, 'otp_verify', 'skey_generate'),
46        //'RPA'        => array('HEX', MD5_RESULTLEN, NULL, 'rpa_generate'),
47    );
48
49
50
51    public function crypt($algorithm)
52    {
53        if (!array_key_exists($algorithm, $this->password_schemes)) {
54            $this->errormsg[] = "This password scheme isn't supported. Check our Wiki!";
55            return false;
56        }
57
58        $scheme = $this->password_schemes[$algorithm];
59        $func = '__'.$scheme[3];
60
61        $this->password = $this->$func($this->plain);
62        //$this->plain = '';
63        return true;
64    }
65
66    public function verify($algorithm, $password)
67    {
68        if (!array_key_exists($algorithm, $this->password_schemes)) {
69            $this->errormsg[] = "This password scheme isn't supported. Check our Wiki!";
70            return false;
71        }
72
73        $scheme = $this->password_schemes[$algorithm];
74        if ($scheme[2] == null) {
75            $this->errormsg[] = "This password scheme doesn't support verification";
76            return false;
77        }
78
79        $func = '__'.$scheme[2];
80        return  $this->$func($this->plain, $password);
81    }
82
83    private function __crypt_verify($plaintext, $password)
84    {
85        $crypted = crypt($plaintext, $password);
86        return strcmp($crypted, $password) == 0;
87    }
88    private function __crypt_generate($plaintext)
89    {
90        $password = crypt($plaintext);
91        return $password;
92    }
93    private function __md5_generate($plaintext)
94    {
95        return $plaintext;
96    }
97    private function __sha1_generate()
98    {
99    }
100    private function __plain_generate()
101    {
102    }
103    private function __cram_md5_generate($plaintext)
104    {
105
106        #http://hg.dovecot.org/dovecot-1.2/file/84373d238073/src/lib/hmac-md5.c
107        #http://hg.dovecot.org/dovecot-1.2/file/84373d238073/src/auth/password-scheme.c cram_md5_generate
108        #am i right that the hmac salt is the plaintext password itself?
109        $salt = $plaintext;
110        if (function_exists('hash_hmac')) {  //Some providers doesn't offers hash access.
111            return hash_hmac('md5', $plaintext, $salt);
112        } else {
113            return custom_hmac('md5', $plaintext, $salt);
114        }
115    }
116
117
118    /**
119     * @return string
120     */
121    public function custom_hmac($algo, $data, $key, $raw_output = false)
122    {
123        $algo = strtolower($algo);
124        $pack = 'H'.strlen($algo('test'));
125        $size = 64;
126        $opad = str_repeat(chr(0x5C), $size);
127        $ipad = str_repeat(chr(0x36), $size);
128
129        if (strlen($key) > $size) {
130            $key = str_pad(pack($pack, $algo($key)), $size, chr(0x00));
131        } else {
132            $key = str_pad($key, $size, chr(0x00));
133        }
134
135        for ($i = 0; $i < strlen($key) - 1; $i++) {
136            $opad[$i] = $opad[$i] ^ $key[$i];
137            $ipad[$i] = $ipad[$i] ^ $key[$i];
138        }
139
140        $output = $algo($opad.pack($pack, $algo($ipad.$data)));
141
142        return ($raw_output) ? pack($pack, $output) : $output;
143    }
144}
145