1: <?php
2:
3: /**
4: * Authentication classes
5: * @package framework
6: * @subpackage auth
7: */
8:
9: /**
10: * Base class for authentication
11: * Creating a new authentication method requires extending this class
12: * and overriding the check_credentials method
13: * @abstract
14: */
15: abstract class Hm_Auth {
16:
17: /* site configuration object */
18: protected $site_config;
19:
20: /* bool flag defining if users are internal */
21: static public $internal_users = false;
22:
23: /**
24: * Assign site config
25: * @param object $config site config
26: */
27: public function __construct($config) {
28: $this->site_config = $config;
29: }
30:
31: /**
32: * This is the method new auth mechs need to override.
33: * @param string $user username
34: * @param string $pass password
35: * @return bool true if the user is authenticated, false otherwise
36: */
37: abstract public function check_credentials($user, $pass);
38:
39: /**
40: * Optional method for auth mech to save login details
41: * @param object $session session object
42: * @return void
43: */
44: public function save_auth_detail($session) {}
45: }
46:
47: /**
48: * Stub for dynamic authentication
49: */
50: class Hm_Auth_Dynamic extends Hm_Auth {
51: public function check_credentials($user, $pass) {
52: return false;
53: }
54: }
55: /**
56: * Authenticate against an included DB
57: */
58: class Hm_Auth_DB extends Hm_Auth {
59:
60: /* bool flag indicating this is an internal user setup */
61: static public $internal_users = true;
62:
63: /* database connection handle */
64: public $dbh;
65:
66: /**
67: * Send the username and password to the configured DB for authentication
68: * @param string $user username
69: * @param string $pass password
70: * @return bool true if authentication worked
71: */
72: public function check_credentials($user, $pass) {
73: $this->connect();
74: $row = Hm_DB::execute($this->dbh, 'select hash from hm_user where username = ?', [$user]);
75: if ($row && !empty($row['hash']) && Hm_Crypt::check_password($pass, $row['hash'])) {
76: return true;
77: }
78: sleep(2);
79: Hm_Debug::add(sprintf('DB AUTH failed for %s', $user));
80: return false;
81: }
82:
83: /**
84: * Delete a user account from the db
85: * @param string $user username
86: * @return bool true if successful
87: */
88: public function delete($user) {
89: $this->connect();
90: if (Hm_DB::execute($this->dbh, 'delete from hm_user where username = ?', [$user])) {
91: return true;
92: }
93: return false;
94: }
95:
96: /**
97: * Create a new or re-use an existing DB connection
98: * @return bool true if the connection is available
99: */
100: protected function connect() {
101: $this->dbh = Hm_DB::connect($this->site_config);
102: if ($this->dbh) {
103: return true;
104: }
105: Hm_Debug::add(sprintf('Unable to connect to the DB auth server %s', $this->site_config->get('db_host')));
106: return false;
107: }
108:
109: /**
110: * Change the password for a user in the DB
111: * @param string $user username
112: * @param string $pass password
113: * @return bool true on success
114: */
115: public function change_pass($user, $pass) {
116: $this->connect();
117: $hash = Hm_Crypt::hash_password($pass);
118: if (Hm_DB::execute($this->dbh, 'update hm_user set hash=? where username=?', [$hash, $user])) {
119: return true;
120: }
121: return false;
122: }
123:
124: /**
125: * Create a new user in the DB
126: * @param string $user username
127: * @param string $pass password
128: * @return integer
129: */
130: public function create($user, $pass) {
131: $this->connect();
132: $result = 0;
133: $res = Hm_DB::execute($this->dbh, 'select username from hm_user where username = ?', [$user]);
134: if (!empty($res)) {
135: //this var will prevent showing print in phpuni tests
136: if(!defined('CYPHT_PHPUNIT_TEST_MODE')) {
137: print("user {$user} already exists\n");
138: }
139: $result = 1;
140: }
141: else {
142: $hash = Hm_Crypt::hash_password($pass);
143: if (Hm_DB::execute($this->dbh, 'insert into hm_user values(?,?)', [$user, $hash])) {
144: $result = 2;
145: }
146: }
147: return $result;
148: }
149: }
150:
151: /**
152: * Authenticate against an IMAP server
153: */
154: class Hm_Auth_IMAP extends Hm_Auth {
155:
156: /**
157: * Assign site config, get required libs
158: * @param object $config site config
159: */
160: public function __construct($config) {
161: $this->site_config = $config;
162: require_once APP_PATH.'modules/imap/hm-imap.php';
163: include_once APP_PATH.'modules/sievefilters/hm-sieve.php';
164: }
165:
166: /* IMAP authentication server settings */
167: private $imap_settings = [];
168:
169: /**
170: * @param object $imap imap connection object
171: * @return boolean
172: */
173: private function check_connection($imap) {
174: $imap->connect($this->imap_settings);
175: if ($imap->get_state() == 'authenticated') {
176: return true;
177: }
178: elseif ($imap->get_state() != 'connected') {
179: Hm_Debug::add($imap->show_debug(true));
180: Hm_Debug::add(sprintf('Unable to connect to the IMAP auth server %s', $this->imap_settings['server']));
181: return false;
182: }
183: else {
184: Hm_Debug::add($imap->show_debug(true));
185: Hm_Debug::add(sprintf('IMAP AUTH failed for %s', $this->imap_settings['username']));
186: return false;
187: }
188: }
189:
190: /**
191: * Send the username and password to the configured IMAP server for authentication
192: * @param string $user username
193: * @param string $pass password
194: * @return bool true if authentication worked
195: */
196: public function check_credentials($user, $pass) {
197: $imap = new Hm_IMAP();
198: list($server, $port, $tls, $sieve_config, $sieve_tls_mode) = get_auth_config($this->site_config, 'imap');
199: if (!$user || !$pass || !$server || !$port) {
200: Hm_Debug::add($imap->show_debug(true));
201: Hm_Debug::add('Invalid IMAP auth configuration settings');
202: return false;
203: }
204: $this->imap_settings = ['server' => $server, 'port' => $port,
205: 'tls' => $tls, 'username' => $user, 'password' => $pass,
206: 'no_caps' => false, 'blacklisted_extensions' => ['enable'],
207: 'sieve_config_host' => $sieve_config,
208: 'sieve_tls' => $sieve_tls_mode
209: ];
210: return $this->check_connection($imap);
211: }
212:
213: /**
214: * Save IMAP server details
215: * @param object $session session object
216: * @return void
217: */
218: public function save_auth_detail($session) {
219: $session->set('imap_auth_server_settings', $this->imap_settings);
220: }
221: }
222:
223: /**
224: * Authenticate against an LDAP server
225: */
226: class Hm_Auth_LDAP extends Hm_Auth {
227:
228: protected $config = [];
229: protected $fh;
230: protected $source = 'ldap';
231:
232: /**
233: * Build connect uri
234: * @return string
235: */
236: private function connect_details() {
237: $prefix = 'ldaps://';
238: $server = $this->apply_config_value('server', 'localhost');
239: $port = $this->apply_config_value('port', 389);
240: if (
241: empty($this->config['enable_tls']) ||
242: $this->config['enable_tls'] === false ||
243: strtolower($this->config['enable_tls']) === "false"
244: ) {
245: $prefix = 'ldap://';
246: }
247: return $prefix.$server.':'.$port;
248: }
249:
250: /**
251: * @param string $name
252: * @param mixed $default
253: * @return mixed
254: */
255: private function apply_config_value($name, $default) {
256: return !empty($this->config[$name]) ? $this->config[$name] : $default;
257: }
258:
259: /**
260: * Check a username and password
261: * @param string $user username
262: * @param string $pass password
263: * @return boolean
264: */
265: public function check_credentials($user, $pass) {
266: list($server, $port, $tls) = get_auth_config($this->site_config, 'ldap');
267: $base_dn = $this->site_config->get('ldap_auth_base_dn', false);
268: $uid_auth_attr = $this->site_config->get('ldap_auth_uid_attr', false);
269: if ($server && $port && $base_dn) {
270: $user = sprintf('%s=%s,%s', $uid_auth_attr, $user, $base_dn);
271: $this->config = [
272: 'server' => $server,
273: 'port' => $port,
274: 'enable_tls' => $tls,
275: 'base_dn' => $base_dn,
276: 'user' => $user,
277: 'pass' => $pass
278: ];
279: return $this->connect();
280: }
281: Hm_Debug::add('Invalid LDAP auth configuration settings');
282: return false;
283: }
284:
285: /**
286: * Connect and auth to the LDAP server
287: * @return boolean
288: */
289: public function connect() {
290: if (!Hm_Functions::function_exists('ldap_connect')) {
291: return false;
292: }
293: $uri = $this->connect_details();
294: $this->fh = @ldap_connect($uri);
295: if ($this->fh) {
296: ldap_set_option($this->fh, LDAP_OPT_PROTOCOL_VERSION, 3);
297: return $this->auth();
298: }
299: Hm_Debug::add(sprintf('Unable to connect to the LDAP auth server %s', $this->config['server']));
300: return false;
301: }
302:
303: /**
304: * Authenticate to the LDAP server
305: * @return boolean
306: */
307: protected function auth() {
308: $result = @ldap_bind($this->fh, $this->config['user'], $this->config['pass']);
309: if (!$result) {
310: Hm_Debug::add(sprintf('LDAP AUTH failed for %s', $this->config['user']));
311: }
312: return $result;
313: }
314: }
315:
316: /*
317: * @param object $config site config object
318: * @param string $prefix settings prefix
319: * @return array
320: */
321: function get_auth_config($config, $prefix) {
322: $server = $config->get($prefix.'_auth_server', false);
323: $port = $config->get($prefix.'_auth_port', false);
324: $tls = $config->get($prefix.'_auth_tls', false);
325: $ret = [$server, $port, $tls];
326: if ($prefix == 'imap') {
327: $ret[] = $config->get($prefix.'_auth_sieve_conf_host', false);
328: $ret[] = $config->get($prefix.'_auth_sieve_tls_mode', false);
329: }
330: return $ret;
331: }
332: