|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* EGroupware EMailAdmin: Wizard to create mail accounts |
|
4
|
|
|
* |
|
5
|
|
|
* @link http://www.egroupware.org |
|
6
|
|
|
* @package emailadmin |
|
7
|
|
|
* @author Ralf Becker <[email protected]> |
|
8
|
|
|
* @copyright (c) 2013-18 by Ralf Becker <[email protected]> |
|
9
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
use EGroupware\Api; |
|
13
|
|
|
use EGroupware\Api\Framework; |
|
14
|
|
|
use EGroupware\Api\Acl; |
|
15
|
|
|
use EGroupware\Api\Etemplate; |
|
16
|
|
|
use EGroupware\Api\Mail; |
|
|
|
|
|
|
17
|
|
|
|
|
18
|
|
|
/** |
|
19
|
|
|
* Wizard to create mail accounts |
|
20
|
|
|
* |
|
21
|
|
|
* Wizard uses follow heuristic to search for IMAP accounts: |
|
22
|
|
|
* 1. query Mozilla ISPDB for domain from email (perfering SSL over STARTTLS over insecure connection) |
|
23
|
|
|
* 2. guessing and verifying in DNS server-names based on domain from email: |
|
24
|
|
|
* - (imap|smtp).$domain, mail.$domain |
|
25
|
|
|
* - MX is *.mail.protection.outlook.com use (outlook|smtp).office365.com |
|
26
|
|
|
* - MX for $domain |
|
27
|
|
|
* - replace host in MX with (imap|smtp) or mail |
|
28
|
|
|
*/ |
|
29
|
|
|
class admin_mail |
|
30
|
|
|
{ |
|
31
|
|
|
/** |
|
32
|
|
|
* Enable logging of IMAP communication to given path, eg. /tmp/autoconfig.log |
|
33
|
|
|
*/ |
|
34
|
|
|
const DEBUG_LOG = null; |
|
35
|
|
|
/** |
|
36
|
|
|
* Connection timeout in seconds used in autoconfig, can and should be really short! |
|
37
|
|
|
*/ |
|
38
|
|
|
const TIMEOUT = 3; |
|
39
|
|
|
/** |
|
40
|
|
|
* Prefix for callback names |
|
41
|
|
|
* |
|
42
|
|
|
* Used as static::APP_CLASS in etemplate::exec(), to allow mail app extending this class. |
|
43
|
|
|
*/ |
|
44
|
|
|
const APP_CLASS = 'admin.admin_mail.'; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* 0: No SSL |
|
48
|
|
|
*/ |
|
49
|
|
|
const SSL_NONE = Mail\Account::SSL_NONE; |
|
50
|
|
|
/** |
|
51
|
|
|
* 1: STARTTLS on regular tcp connection/port |
|
52
|
|
|
*/ |
|
53
|
|
|
const SSL_STARTTLS = Mail\Account::SSL_STARTTLS; |
|
54
|
|
|
/** |
|
55
|
|
|
* 3: SSL (inferior to TLS!) |
|
56
|
|
|
*/ |
|
57
|
|
|
const SSL_SSL = Mail\Account::SSL_SSL; |
|
58
|
|
|
/** |
|
59
|
|
|
* 2: require TLS version 1+, no SSL version 2 or 3 |
|
60
|
|
|
*/ |
|
61
|
|
|
const SSL_TLS = Mail\Account::SSL_TLS; |
|
62
|
|
|
/** |
|
63
|
|
|
* 8: if set, verify certifcate (currently not implemented in Horde_Imap_Client!) |
|
64
|
|
|
*/ |
|
65
|
|
|
const SSL_VERIFY = Mail\Account::SSL_VERIFY; |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* Log exception including trace to error-log, instead of just displaying the message. |
|
69
|
|
|
* |
|
70
|
|
|
* @var boolean |
|
71
|
|
|
*/ |
|
72
|
|
|
public static $debug = false; |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* Methods callable via menuaction |
|
76
|
|
|
* |
|
77
|
|
|
* @var array |
|
78
|
|
|
*/ |
|
79
|
|
|
public $public_functions = array( |
|
80
|
|
|
'add' => true, |
|
81
|
|
|
'edit' => true, |
|
82
|
|
|
'ajax_activeAccounts' => true |
|
83
|
|
|
); |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* Supported ssl types including none |
|
87
|
|
|
* |
|
88
|
|
|
* @var array |
|
89
|
|
|
*/ |
|
90
|
|
|
public static $ssl_types = array( |
|
91
|
|
|
self::SSL_TLS => 'TLS', // SSL with minimum TLS (no SSL v.2 or v.3), requires Horde_Imap_Client-2.16.0/Horde_Socket_Client-1.1.0 |
|
92
|
|
|
self::SSL_SSL => 'SSL', |
|
93
|
|
|
self::SSL_STARTTLS => 'STARTTLS', |
|
94
|
|
|
'no' => 'no', |
|
95
|
|
|
); |
|
96
|
|
|
/** |
|
97
|
|
|
* Convert ssl-type to Horde secure parameter |
|
98
|
|
|
* |
|
99
|
|
|
* @var array |
|
100
|
|
|
*/ |
|
101
|
|
|
public static $ssl2secure = array( |
|
102
|
|
|
'SSL' => 'ssl', |
|
103
|
|
|
'STARTTLS' => 'tls', |
|
104
|
|
|
'TLS' => 'tlsv1', // SSL with minimum TLS (no SSL v.2 or v.3), requires Horde_Imap_Client-2.16.0/Horde_Socket_Client-1.1.0 |
|
105
|
|
|
); |
|
106
|
|
|
/** |
|
107
|
|
|
* Convert ssl-type to eMailAdmin acc_(imap|sieve|smtp)_ssl integer value |
|
108
|
|
|
* |
|
109
|
|
|
* @var array |
|
110
|
|
|
*/ |
|
111
|
|
|
public static $ssl2type = array( |
|
112
|
|
|
'TLS' => self::SSL_TLS, |
|
113
|
|
|
'SSL' => self::SSL_SSL, |
|
114
|
|
|
'STARTTLS' => self::SSL_STARTTLS, |
|
115
|
|
|
'no' => self::SSL_NONE, |
|
116
|
|
|
); |
|
117
|
|
|
|
|
118
|
|
|
/** |
|
119
|
|
|
* Available IMAP login types |
|
120
|
|
|
* |
|
121
|
|
|
* @var array |
|
122
|
|
|
*/ |
|
123
|
|
|
public static $login_types = array( |
|
124
|
|
|
'' => 'Username specified below for all', |
|
125
|
|
|
'standard' => 'username from account', |
|
126
|
|
|
'vmailmgr' => 'username@domainname', |
|
127
|
|
|
//'admin' => 'Username/Password defined by admin', |
|
128
|
|
|
'uidNumber' => 'UserId@domain eg. u1234@domain', |
|
129
|
|
|
'email' => 'EMail-address from account', |
|
130
|
|
|
); |
|
131
|
|
|
|
|
132
|
|
|
/** |
|
133
|
|
|
* Options for further identities |
|
134
|
|
|
* |
|
135
|
|
|
* @var array |
|
136
|
|
|
*/ |
|
137
|
|
|
public static $further_identities = array( |
|
138
|
|
|
0 => 'Forbid users to create identities', |
|
139
|
|
|
1 => 'Allow users to create further identities', |
|
140
|
|
|
2 => 'Allow users to create identities for aliases', |
|
141
|
|
|
); |
|
142
|
|
|
|
|
143
|
|
|
/** |
|
144
|
|
|
* List of domains know to not support Sieve |
|
145
|
|
|
* |
|
146
|
|
|
* Used to switch Sieve off by default, thought users can allways try switching it on. |
|
147
|
|
|
* Testing not existing Sieve with google takes a long time, as ports are open, |
|
148
|
|
|
* but not answering ... |
|
149
|
|
|
* |
|
150
|
|
|
* @var array |
|
151
|
|
|
*/ |
|
152
|
|
|
public static $no_sieve_blacklist = array('gmail.com', 'googlemail.com', 'outlook.office365.com'); |
|
153
|
|
|
|
|
154
|
|
|
/** |
|
155
|
|
|
* Is current use a mail administrator / has run rights for EMailAdmin |
|
156
|
|
|
* |
|
157
|
|
|
* @var boolean |
|
158
|
|
|
*/ |
|
159
|
|
|
protected $is_admin = false; |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* Constructor |
|
163
|
|
|
*/ |
|
164
|
|
|
public function __construct() |
|
165
|
|
|
{ |
|
166
|
|
|
$this->is_admin = isset($GLOBALS['egw_info']['user']['apps']['admin']); |
|
167
|
|
|
|
|
168
|
|
|
// for some reason most translation for account-wizard are in mail |
|
169
|
|
|
Api\Translation::add_app('mail'); |
|
170
|
|
|
|
|
171
|
|
|
// Horde use locale for translation of error messages |
|
172
|
|
|
Api\Preferences::setlocale(LC_MESSAGES); |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Step 1: IMAP account |
|
177
|
|
|
* |
|
178
|
|
|
* @param array $content |
|
179
|
|
|
* @param type $msg |
|
180
|
|
|
*/ |
|
181
|
|
|
public function add(array $content=array(), $msg='', $msg_type='success') |
|
182
|
|
|
{ |
|
183
|
|
|
$tpl = new Etemplate('admin.mailwizard'); |
|
184
|
|
|
if (empty($content['account_id'])) |
|
185
|
|
|
{ |
|
186
|
|
|
$content['account_id'] = $GLOBALS['egw_info']['user']['account_id']; |
|
187
|
|
|
} |
|
188
|
|
|
// add some defaults if not already set (+= does not overwrite existing values!) |
|
189
|
|
|
$content += array( |
|
190
|
|
|
'ident_realname' => $GLOBALS['egw']->accounts->id2name($content['account_id'], 'account_fullname'), |
|
191
|
|
|
'ident_email' => $GLOBALS['egw']->accounts->id2name($content['account_id'], 'account_email'), |
|
192
|
|
|
'acc_imap_port' => 993, |
|
193
|
|
|
'manual_class' => 'emailadmin_manual', |
|
194
|
|
|
); |
|
195
|
|
|
Framework::message($msg ? $msg : (string)$_GET['msg'], $msg_type); |
|
196
|
|
|
|
|
197
|
|
|
if (!empty($content['acc_imap_host']) || !empty($content['acc_imap_username'])) |
|
198
|
|
|
{ |
|
199
|
|
|
$readonlys['button[manual]'] = true; |
|
|
|
|
|
|
200
|
|
|
unset($content['manual_class']); |
|
201
|
|
|
} |
|
202
|
|
|
$tpl->exec(static::APP_CLASS.'autoconfig', $content, array( |
|
203
|
|
|
'acc_imap_ssl' => self::$ssl_types, |
|
204
|
|
|
), $readonlys, $content, 2); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* Try to autoconfig an account |
|
209
|
|
|
* |
|
210
|
|
|
* @param array $content |
|
211
|
|
|
*/ |
|
212
|
|
|
public function autoconfig(array $content) |
|
213
|
|
|
{ |
|
214
|
|
|
// user pressed [Skip IMAP] --> jump to SMTP config |
|
215
|
|
|
if ($content['button'] && key($content['button']) == 'skip_imap') |
|
216
|
|
|
{ |
|
217
|
|
|
unset($content['button']); |
|
218
|
|
|
if (!isset($content['acc_smtp_host'])) $content['acc_smtp_host'] = ''; // do manual mode right away |
|
219
|
|
|
return $this->smtp($content, lang('Skipping IMAP configuration!')); |
|
220
|
|
|
} |
|
221
|
|
|
$content['output'] = ''; |
|
222
|
|
|
$sel_options = $readonlys = array(); |
|
223
|
|
|
|
|
224
|
|
|
$content['connected'] = $connected = false; |
|
225
|
|
|
if (empty($content['acc_imap_username'])) |
|
226
|
|
|
{ |
|
227
|
|
|
$content['acc_imap_username'] = $content['ident_email']; |
|
228
|
|
|
} |
|
229
|
|
|
if (!empty($content['acc_imap_host'])) |
|
230
|
|
|
{ |
|
231
|
|
|
$hosts = array($content['acc_imap_host'] => true); |
|
232
|
|
|
if ($content['acc_imap_port'] > 0 && !in_array($content['acc_imap_port'], array(143,993))) |
|
233
|
|
|
{ |
|
234
|
|
|
$ssl_type = (string)array_search($content['acc_imap_ssl'], self::$ssl2type); |
|
235
|
|
|
if ($ssl_type === '') $ssl_type = 'insecure'; |
|
236
|
|
|
$hosts[$content['acc_imap_host']] = array( |
|
237
|
|
|
$ssl_type => $content['acc_imap_port'], |
|
238
|
|
|
); |
|
239
|
|
|
} |
|
240
|
|
|
} |
|
241
|
|
|
elseif (($ispdb = self::mozilla_ispdb($content['ident_email'])) && count($ispdb['imap'])) |
|
242
|
|
|
{ |
|
243
|
|
|
$content['ispdb'] = $ispdb; |
|
244
|
|
|
$content['output'] .= lang('Using data from Mozilla ISPDB for provider %1', $ispdb['displayName'])."\n"; |
|
|
|
|
|
|
245
|
|
|
$hosts = array(); |
|
246
|
|
|
foreach($ispdb['imap'] as $server) |
|
|
|
|
|
|
247
|
|
|
{ |
|
248
|
|
|
if (!isset($hosts[$server['hostname']])) |
|
249
|
|
|
{ |
|
250
|
|
|
$hosts[$server['hostname']] = array('username' => $server['username']); |
|
251
|
|
|
} |
|
252
|
|
|
if (strtoupper($server['socketType']) == 'SSL') // try TLS first |
|
253
|
|
|
{ |
|
254
|
|
|
$hosts[$server['hostname']]['TLS'] = $server['port']; |
|
255
|
|
|
} |
|
256
|
|
|
$hosts[$server['hostname']][strtoupper($server['socketType'])] = $server['port']; |
|
257
|
|
|
// make sure we prefer SSL over STARTTLS over insecure |
|
258
|
|
|
if (count($hosts[$server['hostname']]) > 2) |
|
259
|
|
|
{ |
|
260
|
|
|
$hosts[$server['hostname']] = self::fix_ssl_order($hosts[$server['hostname']]); |
|
261
|
|
|
} |
|
262
|
|
|
} |
|
263
|
|
|
} |
|
264
|
|
|
else |
|
265
|
|
|
{ |
|
266
|
|
|
$hosts = $this->guess_hosts($content['ident_email'], 'imap'); |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
// iterate over all hosts and try to connect |
|
270
|
|
|
foreach($hosts as $host => $data) |
|
271
|
|
|
{ |
|
272
|
|
|
$content['acc_imap_host'] = $host; |
|
273
|
|
|
// by default we check SSL, STARTTLS and at last an insecure connection |
|
274
|
|
|
if (!is_array($data)) $data = array('TLS' => 993, 'SSL' => 993, 'STARTTLS' => 143, 'insecure' => 143); |
|
275
|
|
|
|
|
276
|
|
|
foreach($data as $ssl => $port) |
|
277
|
|
|
{ |
|
278
|
|
|
if ($ssl === 'username') continue; |
|
279
|
|
|
|
|
280
|
|
|
$content['acc_imap_ssl'] = (int)self::$ssl2type[$ssl]; |
|
281
|
|
|
|
|
282
|
|
|
$e = null; |
|
283
|
|
|
try { |
|
284
|
|
|
$content['output'] .= "\n".Api\DateTime::to('now', 'H:i:s').": Trying $ssl connection to $host:$port ...\n"; |
|
285
|
|
|
$content['acc_imap_port'] = $port; |
|
286
|
|
|
|
|
287
|
|
|
$imap = self::imap_client($content, self::TIMEOUT); |
|
288
|
|
|
|
|
289
|
|
|
//$content['output'] .= array2string($imap->capability()); |
|
290
|
|
|
$imap->login(); |
|
291
|
|
|
$content['output'] .= "\n".lang('Successful connected to %1 server%2.', 'IMAP', ' '.lang('and logged in'))."\n"; |
|
292
|
|
|
if (!$imap->isSecureConnection()) |
|
293
|
|
|
{ |
|
294
|
|
|
$content['output'] .= lang('Connection is NOT secure! Everyone can read eg. your credentials.')."\n"; |
|
295
|
|
|
$content['acc_imap_ssl'] = 'no'; |
|
296
|
|
|
} |
|
297
|
|
|
//$content['output'] .= "\n\n".array2string($imap->capability()); |
|
298
|
|
|
$content['connected'] = $connected = true; |
|
299
|
|
|
break 2; |
|
300
|
|
|
} |
|
301
|
|
|
catch(Horde_Imap_Client_Exception $e) |
|
302
|
|
|
{ |
|
303
|
|
|
switch($e->getCode()) |
|
304
|
|
|
{ |
|
305
|
|
|
case Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED: |
|
306
|
|
|
$content['output'] .= "\n".$e->getMessage()."\n"; |
|
307
|
|
|
break 3; // no need to try other SSL or non-SSL connections, if auth failed |
|
308
|
|
|
|
|
309
|
|
|
case Horde_Imap_Client_Exception::SERVER_CONNECT: |
|
310
|
|
|
$content['output'] .= "\n".$e->getMessage()."\n"; |
|
311
|
|
|
if ($ssl == 'STARTTLS') break 2; // no need to try insecure connection on same port |
|
312
|
|
|
break; |
|
313
|
|
|
|
|
314
|
|
|
default: |
|
315
|
|
|
$content['output'] .= "\n".get_class($e).': '.$e->getMessage().' ('.$e->getCode().')'."\n"; |
|
316
|
|
|
//$content['output'] .= $e->getTraceAsString()."\n"; |
|
317
|
|
|
} |
|
318
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
319
|
|
|
} |
|
320
|
|
|
catch(Exception $e) { |
|
321
|
|
|
$content['output'] .= "\n".get_class($e).': '.$e->getMessage().' ('.$e->getCode().')'."\n"; |
|
322
|
|
|
//$content['output'] .= $e->getTraceAsString()."\n"; |
|
323
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
324
|
|
|
} |
|
325
|
|
|
} |
|
326
|
|
|
} |
|
327
|
|
|
if ($connected) // continue with next wizard step: define folders |
|
328
|
|
|
{ |
|
329
|
|
|
unset($content['button']); |
|
330
|
|
|
return $this->folder($content, lang('Successful connected to %1 server%2.', 'IMAP', ' '.lang('and logged in')). |
|
331
|
|
|
($imap->isSecureConnection() ? '' : "\n".lang('Connection is NOT secure! Everyone can read eg. your credentials.'))); |
|
332
|
|
|
} |
|
333
|
|
|
// add validation error, if we can identify a field |
|
334
|
|
|
if (!$connected && $e instanceof Horde_Imap_Client_Exception) |
|
|
|
|
|
|
335
|
|
|
{ |
|
336
|
|
|
switch($e->getCode()) |
|
337
|
|
|
{ |
|
338
|
|
|
case Horde_Imap_Client_Exception::LOGIN_AUTHENTICATIONFAILED: |
|
339
|
|
|
Etemplate::set_validation_error('acc_imap_username', lang($e->getMessage())); |
|
340
|
|
|
Etemplate::set_validation_error('acc_imap_password', lang($e->getMessage())); |
|
341
|
|
|
break; |
|
342
|
|
|
|
|
343
|
|
|
case Horde_Imap_Client_Exception::SERVER_CONNECT: |
|
344
|
|
|
Etemplate::set_validation_error('acc_imap_host', lang($e->getMessage())); |
|
345
|
|
|
break; |
|
346
|
|
|
} |
|
347
|
|
|
} |
|
348
|
|
|
$readonlys['button[manual]'] = true; |
|
349
|
|
|
unset($content['manual_class']); |
|
350
|
|
|
$sel_options['acc_imap_ssl'] = self::$ssl_types; |
|
351
|
|
|
$tpl = new Etemplate('admin.mailwizard'); |
|
352
|
|
|
$tpl->exec(static::APP_CLASS.'autoconfig', $content, $sel_options, $readonlys, $content, 2); |
|
353
|
|
|
} |
|
354
|
|
|
|
|
355
|
|
|
/** |
|
356
|
|
|
* Step 2: Folder - let user select trash, sent, drafs and template folder |
|
357
|
|
|
* |
|
358
|
|
|
* @param array $content |
|
359
|
|
|
* @param string $msg ='' |
|
360
|
|
|
* @param Horde_Imap_Client_Socket $imap =null |
|
361
|
|
|
*/ |
|
362
|
|
|
public function folder(array $content, $msg='', Horde_Imap_Client_Socket $imap=null) |
|
363
|
|
|
{ |
|
364
|
|
|
if (isset($content['button'])) |
|
365
|
|
|
{ |
|
366
|
|
|
$button = key($content['button']); |
|
367
|
|
|
unset($content['button']); |
|
368
|
|
|
switch($button) |
|
369
|
|
|
{ |
|
370
|
|
|
case 'back': |
|
371
|
|
|
return $this->add($content); |
|
|
|
|
|
|
372
|
|
|
|
|
373
|
|
|
case 'continue': |
|
374
|
|
|
return $this->sieve($content); |
|
375
|
|
|
} |
|
376
|
|
|
} |
|
377
|
|
|
$content['msg'] = $msg; |
|
378
|
|
|
if (!isset($imap)) $imap = self::imap_client ($content); |
|
379
|
|
|
|
|
380
|
|
|
try { |
|
381
|
|
|
//_debug_array($content); |
|
382
|
|
|
$sel_options['acc_folder_sent'] = $sel_options['acc_folder_trash'] = |
|
383
|
|
|
$sel_options['acc_folder_draft'] = $sel_options['acc_folder_template'] = |
|
384
|
|
|
$sel_options['acc_folder_junk'] = $sel_options['acc_folder_archive'] = |
|
385
|
|
|
$sel_options['acc_folder_ham'] = self::mailboxes($imap, $content); |
|
|
|
|
|
|
386
|
|
|
} |
|
387
|
|
|
catch(Exception $e) { |
|
388
|
|
|
$content['msg'] = $e->getMessage(); |
|
389
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
390
|
|
|
} |
|
391
|
|
|
|
|
392
|
|
|
$tpl = new Etemplate('admin.mailwizard.folder'); |
|
393
|
|
|
$tpl->exec(static::APP_CLASS.'folder', $content, $sel_options, array(), $content); |
|
394
|
|
|
} |
|
395
|
|
|
|
|
396
|
|
|
/** |
|
397
|
|
|
* Query mailboxes and (optional) detect special folders |
|
398
|
|
|
* |
|
399
|
|
|
* @param Horde_Imap_Client_Socket $imap |
|
400
|
|
|
* @param array &$content=null on return values for acc_folder_(sent|trash|draft|template) |
|
401
|
|
|
* @return array with folders as key AND value |
|
402
|
|
|
* @throws Horde_Imap_Client_Exception |
|
403
|
|
|
*/ |
|
404
|
|
|
public static function mailboxes(Horde_Imap_Client_Socket $imap, array &$content=null) |
|
405
|
|
|
{ |
|
406
|
|
|
// query all subscribed mailboxes |
|
407
|
|
|
$mailboxes = $imap->listMailboxes('*', Horde_Imap_Client::MBOX_SUBSCRIBED, array( |
|
408
|
|
|
'special_use' => true, |
|
409
|
|
|
'attributes' => true, // otherwise special_use is only queried, but not returned ;-) |
|
410
|
|
|
'delimiter' => true, |
|
411
|
|
|
)); |
|
412
|
|
|
//_debug_array($mailboxes); |
|
413
|
|
|
// list mailboxes by special-use attributes |
|
414
|
|
|
$folders = $attributes = $all = array(); |
|
|
|
|
|
|
415
|
|
|
foreach($mailboxes as $mailbox => $data) |
|
416
|
|
|
{ |
|
417
|
|
|
foreach($data['attributes'] as $attribute) |
|
418
|
|
|
{ |
|
419
|
|
|
$attributes[$attribute][] = $mailbox; |
|
420
|
|
|
} |
|
421
|
|
|
$folders[$mailbox] = $mailbox.': '.implode(', ', $data['attributes']); |
|
422
|
|
|
} |
|
423
|
|
|
// pre-select send, trash, ... folder for user, by checking special-use attributes or common name(s) |
|
424
|
|
|
foreach(array( |
|
425
|
|
|
'acc_folder_sent' => array('\\sent', 'sent'), |
|
426
|
|
|
'acc_folder_trash' => array('\\trash', 'trash'), |
|
427
|
|
|
'acc_folder_draft' => array('\\drafts', 'drafts'), |
|
428
|
|
|
'acc_folder_template' => array('', 'templates'), |
|
429
|
|
|
'acc_folder_junk' => array('\\junk', 'junk', 'spam'), |
|
430
|
|
|
'acc_folder_ham' => array('', 'ham'), |
|
431
|
|
|
'acc_folder_archive' => array('', 'archive'), |
|
432
|
|
|
) as $name => $common_names) |
|
433
|
|
|
{ |
|
434
|
|
|
// first check special-use attributes |
|
435
|
|
|
if (($special_use = array_shift($common_names))) |
|
436
|
|
|
{ |
|
437
|
|
|
foreach((array)$attributes[$special_use] as $mailbox) |
|
438
|
|
|
{ |
|
439
|
|
|
if (empty($content[$name]) || strlen($mailbox) < strlen($content[$name])) |
|
440
|
|
|
{ |
|
441
|
|
|
$content[$name] = $mailbox; |
|
442
|
|
|
} |
|
443
|
|
|
} |
|
444
|
|
|
} |
|
445
|
|
|
// no special use folder found, try common names |
|
446
|
|
|
if (empty($content[$name])) |
|
447
|
|
|
{ |
|
448
|
|
|
foreach($mailboxes as $mailbox => $data) |
|
449
|
|
|
{ |
|
450
|
|
|
$delimiter = !empty($data['delimiter']) ? $data['delimiter'] : '.'; |
|
451
|
|
|
$name_parts = explode($delimiter, strtolower($mailbox)); |
|
452
|
|
|
if (array_intersect($name_parts, $common_names) && |
|
453
|
|
|
(empty($content[$name]) || strlen($mailbox) < strlen($content[$name]) && substr($content[$name], 0, 6) != 'INBOX'.$delimiter)) |
|
454
|
|
|
{ |
|
455
|
|
|
//error_log(__METHOD__."() $mailbox --> ".substr($name, 11).' folder'); |
|
456
|
|
|
$content[$name] = $mailbox; |
|
457
|
|
|
} |
|
458
|
|
|
//else error_log(__METHOD__."() $mailbox does NOT match array_intersect(".array2string($name_parts).', '.array2string($common_names).')='.array2string(array_intersect($name_parts, $common_names))); |
|
459
|
|
|
} |
|
460
|
|
|
} |
|
461
|
|
|
$folders[(string)$content[$name]] .= ' --> '.substr($name, 11).' folder'; |
|
462
|
|
|
} |
|
463
|
|
|
// uncomment for infos about selection process |
|
464
|
|
|
//$content['folder_output'] = implode("\n", $folders); |
|
465
|
|
|
|
|
466
|
|
|
return array_combine(array_keys($mailboxes), array_keys($mailboxes)); |
|
467
|
|
|
} |
|
468
|
|
|
|
|
469
|
|
|
/** |
|
470
|
|
|
* Step 3: Sieve |
|
471
|
|
|
* |
|
472
|
|
|
* @param array $content |
|
473
|
|
|
* @param string $msg ='' |
|
474
|
|
|
*/ |
|
475
|
|
|
public function sieve(array $content, $msg='') |
|
476
|
|
|
{ |
|
477
|
|
|
static $sieve_ssl2port = array( |
|
478
|
|
|
self::SSL_TLS => 5190, |
|
479
|
|
|
self::SSL_SSL => 5190, |
|
480
|
|
|
self::SSL_STARTTLS => array(4190, 2000), |
|
481
|
|
|
self::SSL_NONE => array(4190, 2000), |
|
482
|
|
|
); |
|
483
|
|
|
$content['msg'] = $msg; |
|
484
|
|
|
|
|
485
|
|
|
if (isset($content['button'])) |
|
486
|
|
|
{ |
|
487
|
|
|
$button = key($content['button']); |
|
488
|
|
|
unset($content['button']); |
|
489
|
|
|
switch($button) |
|
490
|
|
|
{ |
|
491
|
|
|
case 'back': |
|
492
|
|
|
return $this->folder($content); |
|
493
|
|
|
|
|
494
|
|
|
case 'continue': |
|
495
|
|
|
if (!$content['acc_sieve_enabled']) |
|
496
|
|
|
{ |
|
497
|
|
|
return $this->smtp($content); |
|
498
|
|
|
} |
|
499
|
|
|
break; |
|
500
|
|
|
} |
|
501
|
|
|
} |
|
502
|
|
|
// first try: hide manual config |
|
503
|
|
|
if (!isset($content['acc_sieve_enabled'])) |
|
504
|
|
|
{ |
|
505
|
|
|
list(, $domain) = explode('@', $content['acc_imap_username']); |
|
506
|
|
|
$content['acc_sieve_enabled'] = (int)!in_array($domain, self::$no_sieve_blacklist); |
|
507
|
|
|
$content['manual_class'] = 'emailadmin_manual'; |
|
508
|
|
|
} |
|
509
|
|
|
else |
|
510
|
|
|
{ |
|
511
|
|
|
unset($content['manual_class']); |
|
512
|
|
|
$readonlys['button[manual]'] = true; |
|
|
|
|
|
|
513
|
|
|
} |
|
514
|
|
|
// set default ssl and port |
|
515
|
|
|
if (!isset($content['acc_sieve_ssl'])) $content['acc_sieve_ssl'] = key(self::$ssl_types); |
|
516
|
|
|
if (empty($content['acc_sieve_port'])) $content['acc_sieve_port'] = $sieve_ssl2port[$content['acc_sieve_ssl']]; |
|
517
|
|
|
|
|
518
|
|
|
// check smtp connection |
|
519
|
|
|
if ($button == 'continue') |
|
520
|
|
|
{ |
|
521
|
|
|
$content['sieve_connected'] = false; |
|
522
|
|
|
$content['sieve_output'] = ''; |
|
523
|
|
|
unset($content['manual_class']); |
|
524
|
|
|
|
|
525
|
|
|
if (empty($content['acc_sieve_host'])) |
|
526
|
|
|
{ |
|
527
|
|
|
$content['acc_sieve_host'] = $content['acc_imap_host']; |
|
528
|
|
|
} |
|
529
|
|
|
// if use set non-standard port, use it |
|
530
|
|
|
if (!in_array($content['acc_sieve_port'], (array)$sieve_ssl2port[$content['acc_sieve_ssl']])) |
|
531
|
|
|
{ |
|
532
|
|
|
$data = array($content['acc_sieve_ssl'] => $content['acc_sieve_port']); |
|
533
|
|
|
} |
|
534
|
|
|
else // otherwise try all standard ports |
|
535
|
|
|
{ |
|
536
|
|
|
$data = $sieve_ssl2port; |
|
537
|
|
|
} |
|
538
|
|
|
foreach($data as $ssl => $ports) |
|
539
|
|
|
{ |
|
540
|
|
|
foreach((array)$ports as $port) |
|
541
|
|
|
{ |
|
542
|
|
|
$content['acc_sieve_ssl'] = $ssl; |
|
543
|
|
|
$ssl_label = self::$ssl_types[$ssl]; |
|
544
|
|
|
|
|
545
|
|
|
$e = null; |
|
|
|
|
|
|
546
|
|
|
try { |
|
547
|
|
|
$content['sieve_output'] .= "\n".Api\DateTime::to('now', 'H:i:s').": Trying $ssl_label connection to $content[acc_sieve_host]:$port ...\n"; |
|
548
|
|
|
$content['acc_sieve_port'] = $port; |
|
549
|
|
|
$sieve = new Horde\ManageSieve(array( |
|
550
|
|
|
'host' => $content['acc_sieve_host'], |
|
551
|
|
|
'port' => $content['acc_sieve_port'], |
|
552
|
|
|
'secure' => self::$ssl2secure[(string)array_search($content['acc_sieve_ssl'], self::$ssl2type)], |
|
553
|
|
|
'timeout' => self::TIMEOUT, |
|
554
|
|
|
'logger' => self::DEBUG_LOG ? new admin_mail_logger(self::DEBUG_LOG) : null, |
|
555
|
|
|
)); |
|
556
|
|
|
// connect to sieve server |
|
557
|
|
|
$sieve->connect(); |
|
558
|
|
|
$content['sieve_output'] .= "\n".lang('Successful connected to %1 server%2.', 'Sieve',''); |
|
|
|
|
|
|
559
|
|
|
// and log in |
|
560
|
|
|
$sieve->login($content['acc_imap_username'], $content['acc_imap_password']); |
|
561
|
|
|
$content['sieve_output'] .= ' '.lang('and logged in')."\n"; |
|
562
|
|
|
$content['sieve_connected'] = true; |
|
563
|
|
|
|
|
564
|
|
|
unset($content['button']); |
|
565
|
|
|
return $this->smtp($content, lang('Successful connected to %1 server%2.', 'Sieve', |
|
566
|
|
|
' '.lang('and logged in'))); |
|
567
|
|
|
} |
|
568
|
|
|
catch(Horde\ManageSieve\Exception\ConnectionFailed $e) { |
|
569
|
|
|
$content['sieve_output'] .= "\n".$e->getMessage().' '.$e->details."\n"; |
|
570
|
|
|
} |
|
571
|
|
|
catch(Exception $e) { |
|
572
|
|
|
$content['sieve_output'] .= "\n".get_class($e).': '.$e->getMessage(). |
|
573
|
|
|
($e->details ? ' '.$e->details : '').' ('.$e->getCode().')'."\n"; |
|
574
|
|
|
$content['sieve_output'] .= $e->getTraceAsString()."\n"; |
|
575
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
576
|
|
|
} |
|
577
|
|
|
} |
|
578
|
|
|
} |
|
579
|
|
|
// not connected, and default ssl/port --> reset again to secure settings |
|
580
|
|
|
if ($data == $sieve_ssl2port) |
|
581
|
|
|
{ |
|
582
|
|
|
$content['acc_sieve_ssl'] = key(self::$ssl_types); |
|
583
|
|
|
$content['acc_sieve_port'] = $sieve_ssl2port[$content['acc_sieve_ssl']]; |
|
584
|
|
|
} |
|
585
|
|
|
} |
|
586
|
|
|
// add validation error, if we can identify a field |
|
587
|
|
|
if (!$content['sieve_connected'] && $e instanceof Exception) |
|
588
|
|
|
{ |
|
589
|
|
|
switch($e->getCode()) |
|
590
|
|
|
{ |
|
591
|
|
|
case 61: // connection refused |
|
592
|
|
|
case 60: // connection timed out (imap.googlemail.com returns that for none-ssl/4190/2000) |
|
593
|
|
|
case 65: // no route ot host (imap.googlemail.com returns that for ssl/5190) |
|
594
|
|
|
Etemplate::set_validation_error('acc_sieve_host', lang($e->getMessage())); |
|
595
|
|
|
Etemplate::set_validation_error('acc_sieve_port', lang($e->getMessage())); |
|
596
|
|
|
break; |
|
597
|
|
|
} |
|
598
|
|
|
$content['msg'] = lang('No sieve support detected, either fix configuration manually or leave it switched off.'); |
|
599
|
|
|
$content['acc_sieve_enabled'] = 0; |
|
600
|
|
|
} |
|
601
|
|
|
$sel_options['acc_sieve_ssl'] = self::$ssl_types; |
|
|
|
|
|
|
602
|
|
|
$tpl = new Etemplate('admin.mailwizard.sieve'); |
|
603
|
|
|
$tpl->exec(static::APP_CLASS.'sieve', $content, $sel_options, $readonlys, $content, 2); |
|
604
|
|
|
} |
|
605
|
|
|
|
|
606
|
|
|
/** |
|
607
|
|
|
* Step 4: SMTP |
|
608
|
|
|
* |
|
609
|
|
|
* @param array $content |
|
610
|
|
|
* @param string $msg ='' |
|
611
|
|
|
*/ |
|
612
|
|
|
public function smtp(array $content, $msg='') |
|
613
|
|
|
{ |
|
614
|
|
|
static $smtp_ssl2port = array( |
|
615
|
|
|
self::SSL_NONE => 25, |
|
616
|
|
|
self::SSL_SSL => 465, |
|
617
|
|
|
self::SSL_TLS => 465, |
|
618
|
|
|
self::SSL_STARTTLS => 587, |
|
619
|
|
|
); |
|
620
|
|
|
$content['msg'] = $msg; |
|
621
|
|
|
|
|
622
|
|
|
if (isset($content['button'])) |
|
623
|
|
|
{ |
|
624
|
|
|
$button = key($content['button']); |
|
625
|
|
|
unset($content['button']); |
|
626
|
|
|
switch($button) |
|
627
|
|
|
{ |
|
628
|
|
|
case 'back': |
|
629
|
|
|
return $this->sieve($content); |
|
630
|
|
|
} |
|
631
|
|
|
} |
|
632
|
|
|
// first try: hide manual config |
|
633
|
|
|
if (!isset($content['acc_smtp_host'])) |
|
634
|
|
|
{ |
|
635
|
|
|
$content['manual_class'] = 'emailadmin_manual'; |
|
636
|
|
|
} |
|
637
|
|
|
else |
|
638
|
|
|
{ |
|
639
|
|
|
unset($content['manual_class']); |
|
640
|
|
|
$readonlys['button[manual]'] = true; |
|
|
|
|
|
|
641
|
|
|
} |
|
642
|
|
|
// copy username/password from imap |
|
643
|
|
|
if (!isset($content['acc_smtp_username'])) $content['acc_smtp_username'] = $content['acc_imap_username']; |
|
644
|
|
|
if (!isset($content['acc_smtp_password'])) $content['acc_smtp_password'] = $content['acc_imap_password']; |
|
645
|
|
|
// set default ssl |
|
646
|
|
|
if (!isset($content['acc_smtp_ssl'])) $content['acc_smtp_ssl'] = key(self::$ssl_types); |
|
647
|
|
|
if (empty($content['acc_smtp_port'])) $content['acc_smtp_port'] = $smtp_ssl2port[$content['acc_smtp_ssl']]; |
|
648
|
|
|
|
|
649
|
|
|
// check smtp connection |
|
650
|
|
|
if ($button == 'continue') |
|
651
|
|
|
{ |
|
652
|
|
|
$content['smtp_connected'] = false; |
|
653
|
|
|
$content['smtp_output'] = ''; |
|
654
|
|
|
unset($content['manual_class']); |
|
655
|
|
|
|
|
656
|
|
|
if (!empty($content['acc_smtp_host'])) |
|
657
|
|
|
{ |
|
658
|
|
|
$hosts = array($content['acc_smtp_host'] => true); |
|
659
|
|
|
if ((string)$content['acc_smtp_ssl'] !== (string)self::SSL_TLS || $content['acc_smtp_port'] != $smtp_ssl2port[$content['acc_smtp_ssl']]) |
|
660
|
|
|
{ |
|
661
|
|
|
$ssl_type = (string)array_search($content['acc_smtp_ssl'], self::$ssl2type); |
|
662
|
|
|
$hosts[$content['acc_smtp_host']] = array( |
|
663
|
|
|
$ssl_type => $content['acc_smtp_port'], |
|
664
|
|
|
); |
|
665
|
|
|
} |
|
666
|
|
|
} |
|
667
|
|
|
elseif($content['ispdb'] && !empty($content['ispdb']['smtp'])) |
|
668
|
|
|
{ |
|
669
|
|
|
$content['smtp_output'] .= lang('Using data from Mozilla ISPDB for provider %1', $content['ispdb']['displayName'])."\n"; |
|
|
|
|
|
|
670
|
|
|
$hosts = array(); |
|
671
|
|
|
foreach($content['ispdb']['smtp'] as $server) |
|
672
|
|
|
{ |
|
673
|
|
|
if (!isset($hosts[$server['hostname']])) |
|
674
|
|
|
{ |
|
675
|
|
|
$hosts[$server['hostname']] = array('username' => $server['username']); |
|
676
|
|
|
} |
|
677
|
|
|
if (strtoupper($server['socketType']) == 'SSL') // try TLS first |
|
678
|
|
|
{ |
|
679
|
|
|
$hosts[$server['hostname']]['TLS'] = $server['port']; |
|
680
|
|
|
} |
|
681
|
|
|
$hosts[$server['hostname']][strtoupper($server['socketType'])] = $server['port']; |
|
682
|
|
|
// make sure we prefer SSL over STARTTLS over insecure |
|
683
|
|
|
if (count($hosts[$server['hostname']]) > 2) |
|
684
|
|
|
{ |
|
685
|
|
|
$hosts[$server['hostname']] = self::fix_ssl_order($hosts[$server['hostname']]); |
|
686
|
|
|
} |
|
687
|
|
|
} |
|
688
|
|
|
} |
|
689
|
|
|
else |
|
690
|
|
|
{ |
|
691
|
|
|
$hosts = $this->guess_hosts($content['ident_email'], 'smtp'); |
|
692
|
|
|
} |
|
693
|
|
|
foreach($hosts as $host => $data) |
|
694
|
|
|
{ |
|
695
|
|
|
$content['acc_smtp_host'] = $host; |
|
696
|
|
|
if (!is_array($data)) |
|
697
|
|
|
{ |
|
698
|
|
|
$data = array('TLS' => 465, 'SSL' => 465, 'STARTTLS' => 587, '' => 25); |
|
699
|
|
|
} |
|
700
|
|
|
foreach($data as $ssl => $port) |
|
701
|
|
|
{ |
|
702
|
|
|
if ($ssl === 'username') continue; |
|
703
|
|
|
|
|
704
|
|
|
$content['acc_smtp_ssl'] = (int)self::$ssl2type[$ssl]; |
|
705
|
|
|
|
|
706
|
|
|
$e = null; |
|
|
|
|
|
|
707
|
|
|
try { |
|
708
|
|
|
$content['smtp_output'] .= "\n".Api\DateTime::to('now', 'H:i:s').": Trying $ssl connection to $host:$port ...\n"; |
|
709
|
|
|
$content['acc_smtp_port'] = $port; |
|
710
|
|
|
|
|
711
|
|
|
$mail = new Horde_Mail_Transport_Smtphorde($params=array( |
|
712
|
|
|
'username' => $content['acc_smtp_username'], |
|
713
|
|
|
'password' => $content['acc_smtp_password'], |
|
714
|
|
|
'host' => $content['acc_smtp_host'], |
|
715
|
|
|
'port' => $content['acc_smtp_port'], |
|
716
|
|
|
'secure' => self::$ssl2secure[(string)array_search($content['acc_smtp_ssl'], self::$ssl2type)], |
|
717
|
|
|
'timeout' => self::TIMEOUT, |
|
718
|
|
|
'debug' => self::DEBUG_LOG, |
|
719
|
|
|
)); |
|
720
|
|
|
// create smtp connection and authenticate, if credentials given |
|
721
|
|
|
$smtp = $mail->getSMTPObject(); |
|
722
|
|
|
$content['smtp_output'] .= "\n".lang('Successful connected to %1 server%2.', 'SMTP', |
|
723
|
|
|
(!empty($content['acc_smtp_username']) ? ' '.lang('and logged in') : ''))."\n"; |
|
724
|
|
|
if (!$smtp->isSecureConnection()) |
|
725
|
|
|
{ |
|
726
|
|
|
if (!empty($content['acc_smtp_username'])) |
|
727
|
|
|
{ |
|
728
|
|
|
$content['smtp_output'] .= lang('Connection is NOT secure! Everyone can read eg. your credentials.')."\n"; |
|
729
|
|
|
} |
|
730
|
|
|
$content['acc_smtp_ssl'] = 'no'; |
|
731
|
|
|
} |
|
732
|
|
|
// Horde_Smtp always try to use STARTTLS, adjust our ssl-parameter if successful |
|
733
|
|
|
elseif (!($content['acc_smtp_ssl'] > self::SSL_NONE)) |
|
734
|
|
|
{ |
|
735
|
|
|
//error_log(__METHOD__."() new Horde_Mail_Transport_Smtphorde(".array2string($params).")->getSMTPObject()->isSecureConnection()=".array2string($smtp->isSecureConnection())); |
|
736
|
|
|
$content['acc_smtp_ssl'] = self::SSL_STARTTLS; |
|
737
|
|
|
} |
|
738
|
|
|
// try sending a mail to a different domain, if not authenticated, to see if that's required |
|
739
|
|
|
if (empty($content['acc_smtp_username'])) |
|
740
|
|
|
{ |
|
741
|
|
|
$smtp->send($content['ident_email'], '[email protected]', ''); |
|
742
|
|
|
$content['smtp_output'] .= "\n".lang('Relay access checked')."\n"; |
|
743
|
|
|
} |
|
744
|
|
|
$content['smtp_connected'] = true; |
|
745
|
|
|
unset($content['button']); |
|
746
|
|
|
return $this->edit($content, lang('Successful connected to %1 server%2.', 'SMTP', |
|
747
|
|
|
empty($content['acc_smtp_username']) ? ' - '.lang('Relay access checked') : ' '.lang('and logged in'))); |
|
748
|
|
|
} |
|
749
|
|
|
// unfortunately LOGIN_AUTHENTICATIONFAILED and SERVER_CONNECT are thrown as Horde_Mail_Exception |
|
750
|
|
|
// while others are thrown as Horde_Smtp_Exception --> using common base Horde_Exception_Wrapped |
|
751
|
|
|
catch(Horde_Exception_Wrapped $e) |
|
752
|
|
|
{ |
|
753
|
|
|
switch($e->getCode()) |
|
754
|
|
|
{ |
|
755
|
|
|
case Horde_Smtp_Exception::LOGIN_AUTHENTICATIONFAILED: |
|
756
|
|
|
case Horde_Smtp_Exception::LOGIN_REQUIREAUTHENTICATION: |
|
757
|
|
|
case Horde_Smtp_Exception::UNSPECIFIED: |
|
758
|
|
|
$content['smtp_output'] .= "\n".$e->getMessage()."\n"; |
|
759
|
|
|
break; |
|
760
|
|
|
case Horde_Smtp_Exception::SERVER_CONNECT: |
|
761
|
|
|
$content['smtp_output'] .= "\n".$e->getMessage()."\n"; |
|
762
|
|
|
break; |
|
763
|
|
|
default: |
|
764
|
|
|
$content['smtp_output'] .= "\n".$e->getMessage().' ('.$e->getCode().')'."\n"; |
|
765
|
|
|
break; |
|
766
|
|
|
} |
|
767
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
768
|
|
|
} |
|
769
|
|
|
catch(Horde_Smtp_Exception $e) |
|
770
|
|
|
{ |
|
771
|
|
|
// prever $e->details over $e->getMessage() as it contains original message from SMTP server (eg. relay access denied) |
|
772
|
|
|
$content['smtp_output'] .= "\n".(empty($e->details) ? $e->getMessage().' ('.$e->getCode().')' : $e->details)."\n"; |
|
773
|
|
|
//$content['smtp_output'] .= $e->getTraceAsString()."\n"; |
|
774
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
775
|
|
|
} |
|
776
|
|
|
catch(Exception $e) { |
|
777
|
|
|
$content['smtp_output'] .= "\n".get_class($e).': '.$e->getMessage().' ('.$e->getCode().')'."\n"; |
|
778
|
|
|
//$content['smtp_output'] .= $e->getTraceAsString()."\n"; |
|
779
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
780
|
|
|
} |
|
781
|
|
|
} |
|
782
|
|
|
} |
|
783
|
|
|
} |
|
784
|
|
|
// add validation error, if we can identify a field |
|
785
|
|
|
if (!$content['smtp_connected'] && $e instanceof Horde_Exception_Wrapped) |
|
786
|
|
|
{ |
|
787
|
|
|
switch($e->getCode()) |
|
788
|
|
|
{ |
|
789
|
|
|
case Horde_Smtp_Exception::LOGIN_AUTHENTICATIONFAILED: |
|
790
|
|
|
case Horde_Smtp_Exception::LOGIN_REQUIREAUTHENTICATION: |
|
791
|
|
|
case Horde_Smtp_Exception::UNSPECIFIED: |
|
792
|
|
|
Etemplate::set_validation_error('acc_smtp_username', lang($e->getMessage())); |
|
793
|
|
|
Etemplate::set_validation_error('acc_smtp_password', lang($e->getMessage())); |
|
794
|
|
|
break; |
|
795
|
|
|
|
|
796
|
|
|
case Horde_Smtp_Exception::SERVER_CONNECT: |
|
797
|
|
|
Etemplate::set_validation_error('acc_smtp_host', lang($e->getMessage())); |
|
798
|
|
|
Etemplate::set_validation_error('acc_smtp_port', lang($e->getMessage())); |
|
799
|
|
|
break; |
|
800
|
|
|
} |
|
801
|
|
|
} |
|
802
|
|
|
$sel_options['acc_smtp_ssl'] = self::$ssl_types; |
|
|
|
|
|
|
803
|
|
|
$tpl = new Etemplate('admin.mailwizard.smtp'); |
|
804
|
|
|
$tpl->exec(static::APP_CLASS.'smtp', $content, $sel_options, $readonlys, $content, 2); |
|
805
|
|
|
} |
|
806
|
|
|
|
|
807
|
|
|
/** |
|
808
|
|
|
* Edit mail account(s) |
|
809
|
|
|
* |
|
810
|
|
|
* Gets either called with GET parameter: |
|
811
|
|
|
* |
|
812
|
|
|
* a) account_id from admin >> Manage users to edit / add mail accounts for a user |
|
813
|
|
|
* --> shows selectbox to switch between different mail accounts of user and "create new account" |
|
814
|
|
|
* |
|
815
|
|
|
* b) via mail_wizard proxy class by regular mail user to edit (acc_id GET parameter) or create new mail account |
|
816
|
|
|
* |
|
817
|
|
|
* @param array $content =null |
|
818
|
|
|
* @param string $msg ='' |
|
819
|
|
|
* @param string $msg_type ='success' |
|
820
|
|
|
*/ |
|
821
|
|
|
public function edit(array $content=null, $msg='', $msg_type='success') |
|
822
|
|
|
{ |
|
823
|
|
|
// app is trying to tell something, while redirecting to wizard |
|
824
|
|
|
if (empty($content) && $_GET['acc_id'] && empty($msg) && !empty( $_GET['msg'])) |
|
825
|
|
|
{ |
|
826
|
|
|
if (stripos($_GET['msg'],'fatal error:')!==false || $_GET['msg_type'] == 'error') $msg_type = 'error'; |
|
827
|
|
|
} |
|
828
|
|
|
if ($content['acc_id'] || (isset($_GET['acc_id']) && (int)$_GET['acc_id'] > 0) ) Mail::unsetCachedObjects($content['acc_id']?$content['acc_id']:$_GET['acc_id']); |
|
829
|
|
|
$tpl = new Etemplate('admin.mailaccount'); |
|
830
|
|
|
|
|
831
|
|
|
if (!is_array($content) || !empty($content['acc_id']) && isset($content['old_acc_id']) && $content['acc_id'] != $content['old_acc_id']) |
|
832
|
|
|
{ |
|
833
|
|
|
if (!is_array($content)) $content = array(); |
|
|
|
|
|
|
834
|
|
|
if ($this->is_admin && isset($_GET['account_id'])) |
|
835
|
|
|
{ |
|
836
|
|
|
$content['called_for'] = (int)$_GET['account_id']; |
|
837
|
|
|
$content['accounts'] = iterator_to_array(Mail\Account::search($content['called_for'])); |
|
838
|
|
|
if ($content['accounts']) |
|
839
|
|
|
{ |
|
840
|
|
|
$content['acc_id'] = key($content['accounts']); |
|
841
|
|
|
//error_log(__METHOD__.__LINE__.'.'.array2string($content['acc_id'])); |
|
842
|
|
|
// test if the "to be selected" acccount is imap or not |
|
843
|
|
|
if (is_array($content['accounts']) && count($content['accounts'])>1 && Mail\Account::is_multiple($content['acc_id'])) |
|
844
|
|
|
{ |
|
845
|
|
|
try { |
|
846
|
|
|
$account = Mail\Account::read($content['acc_id'], $content['called_for']); |
|
847
|
|
|
//try to select the first account that is of type imap |
|
848
|
|
|
if (!$account->is_imap()) |
|
849
|
|
|
{ |
|
850
|
|
|
$content['acc_id'] = key($content['accounts']); |
|
851
|
|
|
//error_log(__METHOD__.__LINE__.'.'.array2string($content['acc_id'])); |
|
852
|
|
|
} |
|
853
|
|
|
} |
|
854
|
|
|
catch(Api\Exception\NotFound $e) { |
|
855
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
856
|
|
|
} |
|
857
|
|
|
} |
|
858
|
|
|
} |
|
859
|
|
|
if (!$content['accounts']) // no email account, call wizard |
|
860
|
|
|
{ |
|
861
|
|
|
return $this->add(array('account_id' => (int)$_GET['account_id'])); |
|
|
|
|
|
|
862
|
|
|
} |
|
863
|
|
|
$content['accounts']['new'] = lang('Create new account'); |
|
864
|
|
|
} |
|
865
|
|
|
if (isset($_GET['acc_id']) && (int)$_GET['acc_id'] > 0) |
|
866
|
|
|
{ |
|
867
|
|
|
$content['acc_id'] = (int)$_GET['acc_id']; |
|
868
|
|
|
} |
|
869
|
|
|
// clear current account-data, as account has changed and we going to read selected one |
|
870
|
|
|
$content = array_intersect_key($content, array_flip(array('called_for', 'accounts', 'acc_id', 'tabs'))); |
|
871
|
|
|
|
|
872
|
|
|
if ($content['acc_id'] > 0) |
|
873
|
|
|
{ |
|
874
|
|
|
try { |
|
875
|
|
|
$account = Mail\Account::read($content['acc_id'], $this->is_admin && $content['called_for'] ? |
|
876
|
|
|
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id']); |
|
877
|
|
|
$account->getUserData(); // quota, aliases, forwards etc. |
|
878
|
|
|
$content += $account->params; |
|
879
|
|
|
$content['acc_sieve_enabled'] = (string)($content['acc_sieve_enabled']); |
|
880
|
|
|
$content['notify_use_default'] = !$content['notify_account_id']; |
|
881
|
|
|
self::fix_account_id_0($content['account_id']); |
|
882
|
|
|
|
|
883
|
|
|
// read identities (of current user) and mark std identity |
|
884
|
|
|
$content['identities'] = iterator_to_array(Mail\Account::identities($account, true, 'name', $content['called_for'])); |
|
885
|
|
|
$content['std_ident_id'] = $content['ident_id']; |
|
886
|
|
|
$content['identities'][$content['std_ident_id']] = lang('Standard identity'); |
|
887
|
|
|
// change self::SSL_NONE (=0) to "no" used in sel_options |
|
888
|
|
|
foreach(array('imap','smtp','sieve') as $type) |
|
889
|
|
|
{ |
|
890
|
|
|
if (!$content['acc_'.$type.'_ssl']) $content['acc_'.$type.'_ssl'] = 'no'; |
|
891
|
|
|
} |
|
892
|
|
|
} |
|
893
|
|
|
catch(Api\Exception\NotFound $e) { |
|
894
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
895
|
|
|
Framework::window_close(lang('Account not found!')); |
|
896
|
|
|
} |
|
897
|
|
|
catch(Exception $e) { |
|
898
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
899
|
|
|
Framework::window_close($e->getMessage().' ('.get_class($e).': '.$e->getCode().')'); |
|
900
|
|
|
} |
|
901
|
|
|
} |
|
902
|
|
|
elseif ($content['acc_id'] === 'new') |
|
903
|
|
|
{ |
|
904
|
|
|
$content['account_id'] = $content['called_for']; |
|
905
|
|
|
$content['old_acc_id'] = $content['acc_id']; // to not call add/wizard, if we return from to |
|
906
|
|
|
unset($content['tabs']); |
|
907
|
|
|
return $this->add($content); |
|
|
|
|
|
|
908
|
|
|
} |
|
909
|
|
|
} |
|
910
|
|
|
// some defaults for new accounts |
|
911
|
|
|
if (!isset($content['account_id']) || empty($content['acc_id']) || $content['acc_id'] === 'new') |
|
912
|
|
|
{ |
|
913
|
|
|
if (!isset($content['account_id'])) $content['account_id'] = array($GLOBALS['egw_info']['user']['account_id']); |
|
914
|
|
|
$content['acc_user_editable'] = $content['acc_further_identities'] = true; |
|
915
|
|
|
$readonlys['ident_id'] = true; // need to create standard identity first |
|
|
|
|
|
|
916
|
|
|
} |
|
917
|
|
|
if (empty($content['acc_name'])) |
|
918
|
|
|
{ |
|
919
|
|
|
$content['acc_name'] = $content['ident_email']; |
|
920
|
|
|
} |
|
921
|
|
|
// disable some stuff for non-emailadmins (all values are preserved!) |
|
922
|
|
|
if (!$this->is_admin) |
|
923
|
|
|
{ |
|
924
|
|
|
$readonlys = array( |
|
925
|
|
|
'account_id' => true, 'button[multiple]' => true, 'acc_user_editable' => true, |
|
926
|
|
|
'acc_further_identities' => true, |
|
927
|
|
|
'acc_imap_type' => true, 'acc_imap_logintype' => true, 'acc_domain' => true, |
|
928
|
|
|
'acc_imap_admin_username' => true, 'acc_imap_admin_password' => true, |
|
929
|
|
|
'acc_smtp_type' => true, 'acc_smtp_auth_session' => true, |
|
930
|
|
|
); |
|
931
|
|
|
} |
|
932
|
|
|
// ensure correct values for single user mail accounts (we only hide them client-side) |
|
933
|
|
|
if (!($is_multiple = Mail\Account::is_multiple($content))) |
|
934
|
|
|
{ |
|
935
|
|
|
$content['acc_imap_type'] = 'EGroupware\\Api\\Mail\\Imap'; |
|
936
|
|
|
unset($content['acc_imap_login_type']); |
|
937
|
|
|
$content['acc_smtp_type'] = 'EGroupware\\Api\\Mail\\Smtp'; |
|
938
|
|
|
unset($content['acc_smtp_auth_session']); |
|
939
|
|
|
unset($content['notify_use_default']); |
|
940
|
|
|
} |
|
941
|
|
|
// copy ident_email_alias selectbox back to regular name |
|
942
|
|
|
elseif (isset($content['ident_email_alias']) && !empty ($content['ident_email_alias'])) |
|
943
|
|
|
{ |
|
944
|
|
|
$content['ident_email'] = $content['ident_email_alias']; |
|
945
|
|
|
} |
|
946
|
|
|
$edit_access = Mail\Account::check_access(Acl::EDIT, $content); |
|
947
|
|
|
|
|
948
|
|
|
// disable notification save-default and use-default, if only one account or no edit-rights |
|
949
|
|
|
$tpl->disableElement('notify_save_default', !$is_multiple || !$edit_access); |
|
950
|
|
|
$tpl->disableElement('notify_use_default', !$is_multiple); |
|
951
|
|
|
|
|
952
|
|
|
if (isset($content['button'])) |
|
953
|
|
|
{ |
|
954
|
|
|
$button = key($content['button']); |
|
955
|
|
|
unset($content['button']); |
|
956
|
|
|
switch($button) |
|
957
|
|
|
{ |
|
958
|
|
|
case 'wizard': |
|
959
|
|
|
// if we just came from wizard, go back to last page/step |
|
960
|
|
|
if (isset($content['smtp_connected'])) |
|
961
|
|
|
{ |
|
962
|
|
|
return $this->smtp($content); |
|
|
|
|
|
|
963
|
|
|
} |
|
964
|
|
|
// otherwise start with first step |
|
965
|
|
|
return $this->autoconfig($content); |
|
966
|
|
|
|
|
967
|
|
|
case 'delete_identity': |
|
968
|
|
|
// delete none-standard identity of current user |
|
969
|
|
|
if (($this->is_admin || $content['acc_further_identities']) && |
|
970
|
|
|
$content['ident_id'] > 0 && $content['std_ident_id'] != $content['ident_id']) |
|
971
|
|
|
{ |
|
972
|
|
|
Mail\Account::delete_identity($content['ident_id']); |
|
973
|
|
|
$msg = lang('Identity deleted'); |
|
974
|
|
|
unset($content['identities'][$content['ident_id']]); |
|
975
|
|
|
$content['ident_id'] = $content['std_ident_id']; |
|
976
|
|
|
} |
|
977
|
|
|
break; |
|
978
|
|
|
|
|
979
|
|
|
case 'save': |
|
980
|
|
|
case 'apply': |
|
981
|
|
|
try { |
|
982
|
|
|
// save none-standard identity for current user |
|
983
|
|
|
if ($content['acc_id'] && $content['acc_id'] !== 'new' && |
|
984
|
|
|
($this->is_admin || $content['acc_further_identities']) && |
|
985
|
|
|
$content['std_ident_id'] != $content['ident_id']) |
|
986
|
|
|
{ |
|
987
|
|
|
$content['ident_id'] = Mail\Account::save_identity(array( |
|
988
|
|
|
'account_id' => $content['called_for'] ? $content['called_for'] : $GLOBALS['egw_info']['user']['account_id'], |
|
989
|
|
|
)+$content); |
|
990
|
|
|
$content['identities'][$content['ident_id']] = Mail\Account::identity_name($content); |
|
991
|
|
|
$msg = lang('Identity saved.'); |
|
992
|
|
|
if ($edit_access) $msg .= ' '.lang('Switch back to standard identity to save account.'); |
|
993
|
|
|
} |
|
994
|
|
|
elseif ($edit_access) |
|
995
|
|
|
{ |
|
996
|
|
|
// if admin username/password given, check if it is valid |
|
997
|
|
|
$account = new Mail\Account($content); |
|
998
|
|
|
if ($account->acc_imap_administration) |
|
999
|
|
|
{ |
|
1000
|
|
|
$imap = $account->imapServer(true); |
|
1001
|
|
|
if ($imap) $imap->checkAdminConnection(); |
|
|
|
|
|
|
1002
|
|
|
} |
|
1003
|
|
|
// test sieve connection, if not called for other user, enabled and credentials available |
|
1004
|
|
|
if (!$content['called_for'] && $account->acc_sieve_enabled && $account->acc_imap_username) |
|
1005
|
|
|
{ |
|
1006
|
|
|
$account->imapServer()->retrieveRules(); |
|
|
|
|
|
|
1007
|
|
|
} |
|
1008
|
|
|
$new_account = !($content['acc_id'] > 0); |
|
1009
|
|
|
// check for deliveryMode="forwardOnly", if a forwarding-address is given |
|
1010
|
|
|
if ($content['acc_smtp_type'] != 'EGroupware\\Api\\Mail\\Smtp' && |
|
1011
|
|
|
$content['deliveryMode'] == Mail\Smtp::FORWARD_ONLY && |
|
1012
|
|
|
empty($content['mailForwardingAddress'])) |
|
1013
|
|
|
{ |
|
1014
|
|
|
Etemplate::set_validation_error('mailForwardingAddress', lang('Field must not be empty !!!')); |
|
1015
|
|
|
throw new Api\Exception\WrongUserinput(lang('You need to specify a forwarding address, when checking "%1"!', lang('Forward only'))); |
|
|
|
|
|
|
1016
|
|
|
} |
|
1017
|
|
|
// set notifications to store according to checkboxes |
|
1018
|
|
|
if ($content['notify_save_default']) |
|
1019
|
|
|
{ |
|
1020
|
|
|
$content['notify_account_id'] = 0; |
|
1021
|
|
|
} |
|
1022
|
|
|
elseif (!$content['notify_use_default']) |
|
1023
|
|
|
{ |
|
1024
|
|
|
$content['notify_account_id'] = $content['called_for'] ? |
|
1025
|
|
|
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id']; |
|
1026
|
|
|
} |
|
1027
|
|
|
// SMIME SAVE |
|
1028
|
|
|
if (isset($content['smimeKeyUpload'])) |
|
1029
|
|
|
{ |
|
1030
|
|
|
$content['acc_smime_cred_id'] = self::save_smime_key($content, $tpl, $content['called_for']); |
|
1031
|
|
|
unset($content['smimeKeyUpload']); |
|
1032
|
|
|
} |
|
1033
|
|
|
self::fix_account_id_0($content['account_id'], true); |
|
1034
|
|
|
$content = Mail\Account::write($content, $content['called_for'] || !$this->is_admin ? |
|
1035
|
|
|
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id']); |
|
1036
|
|
|
self::fix_account_id_0($content['account_id']); |
|
1037
|
|
|
$msg = lang('Account saved.'); |
|
1038
|
|
|
// user wants default notifications |
|
1039
|
|
|
if ($content['acc_id'] && $content['notify_use_default']) |
|
1040
|
|
|
{ |
|
1041
|
|
|
// delete own ones |
|
1042
|
|
|
Mail\Notifications::delete($content['acc_id'], $content['called_for'] ? |
|
1043
|
|
|
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id']); |
|
1044
|
|
|
// load default ones |
|
1045
|
|
|
$content = array_merge($content, Mail\Notifications::read($content['acc_id'], 0)); |
|
1046
|
|
|
} |
|
1047
|
|
|
// add new std identity entry |
|
1048
|
|
|
if ($new_account) |
|
1049
|
|
|
{ |
|
1050
|
|
|
$content['std_ident_id'] = $content['ident_id']; |
|
1051
|
|
|
$content['identities'] = array( |
|
1052
|
|
|
$content['std_ident_id'] => lang('Standard identity')); |
|
1053
|
|
|
} |
|
1054
|
|
|
if (isset($content['accounts'])) |
|
1055
|
|
|
{ |
|
1056
|
|
|
if (!isset($content['accounts'][$content['acc_id']])) // insert new account as top, not bottom |
|
1057
|
|
|
{ |
|
1058
|
|
|
$content['accounts'] = array($content['acc_id'] => '') + $content['accounts']; |
|
1059
|
|
|
} |
|
1060
|
|
|
$content['accounts'][$content['acc_id']] = Mail\Account::identity_name($content, false); |
|
1061
|
|
|
} |
|
1062
|
|
|
} |
|
1063
|
|
|
else |
|
1064
|
|
|
{ |
|
1065
|
|
|
if ($content['notify_use_default'] && $content['notify_account_id']) |
|
1066
|
|
|
{ |
|
1067
|
|
|
// delete own ones |
|
1068
|
|
|
if (Mail\Notifications::delete($content['acc_id'], $content['called_for'] ? |
|
1069
|
|
|
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id'])) |
|
1070
|
|
|
{ |
|
1071
|
|
|
$msg = lang('Notification folders updated.'); |
|
1072
|
|
|
} |
|
1073
|
|
|
// load default ones |
|
1074
|
|
|
$content = array_merge($content, Mail\Notifications::read($content['acc_id'], 0)); |
|
1075
|
|
|
} |
|
1076
|
|
|
if (!$content['notify_use_default'] && is_array($content['notify_folders'])) |
|
1077
|
|
|
{ |
|
1078
|
|
|
$content['notify_account_id'] = $content['called_for'] ? |
|
1079
|
|
|
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id']; |
|
1080
|
|
|
if (Mail\Notifications::write($content['acc_id'], $content['notify_account_id'], |
|
1081
|
|
|
$content['notify_folders'])) |
|
1082
|
|
|
{ |
|
1083
|
|
|
$msg = lang('Notification folders updated.'); |
|
1084
|
|
|
} |
|
1085
|
|
|
} |
|
1086
|
|
|
if ($content['acc_user_forward'] && !empty($content['acc_smtp_type']) && $content['acc_smtp_type'] != 'EGroupware\\Api\\Mail\\Smtp') |
|
1087
|
|
|
{ |
|
1088
|
|
|
$account = new Mail\Account($content); |
|
1089
|
|
|
$account->smtpServer()->saveSMTPForwarding($content['called_for'] ? |
|
1090
|
|
|
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id'], |
|
1091
|
|
|
$content['mailForwardingAddress'], |
|
1092
|
|
|
$content['forwardOnly'] ? null : 'yes'); |
|
1093
|
|
|
} |
|
1094
|
|
|
// smime (private) key uploaded by user himself |
|
1095
|
|
|
if (!empty($content['smimeKeyUpload'])) |
|
1096
|
|
|
{ |
|
1097
|
|
|
$content['acc_smime_cred_id'] = self::save_smime_key($content, $tpl); |
|
1098
|
|
|
unset($content['smimeKeyUpload']); |
|
1099
|
|
|
} |
|
1100
|
|
|
} |
|
1101
|
|
|
} |
|
1102
|
|
|
catch (Horde_Imap_Client_Exception $e) |
|
1103
|
|
|
{ |
|
1104
|
|
|
_egw_log_exception($e); |
|
1105
|
|
|
$tpl->set_validation_error('acc_imap_admin_username', $msg=lang($e->getMessage()).($e->details?', '.lang($e->details):'')); |
|
1106
|
|
|
$msg_type = 'error'; |
|
1107
|
|
|
$content['tabs'] = 'admin.mailaccount.imap'; // should happen automatic |
|
1108
|
|
|
break; |
|
1109
|
|
|
} |
|
1110
|
|
|
catch (Horde\ManageSieve\Exception\ConnectionFailed $e) |
|
1111
|
|
|
{ |
|
1112
|
|
|
_egw_log_exception($e); |
|
1113
|
|
|
$tpl->set_validation_error('acc_sieve_port', $msg=lang($e->getMessage())); |
|
1114
|
|
|
$msg_type = 'error'; |
|
1115
|
|
|
$content['tabs'] = 'admin.mailaccount.sieve'; // should happen automatic |
|
1116
|
|
|
break; |
|
1117
|
|
|
} |
|
1118
|
|
|
catch (Exception $e) { |
|
1119
|
|
|
$msg = lang('Error saving account!')."\n".$e->getMessage(); |
|
1120
|
|
|
$button = 'apply'; |
|
1121
|
|
|
$msg_type = 'error'; |
|
1122
|
|
|
} |
|
1123
|
|
|
if ($content['acc_id']) Mail::unsetCachedObjects($content['acc_id']); |
|
1124
|
|
|
if (stripos($msg,'fatal error:')!==false) $msg_type = 'error'; |
|
1125
|
|
|
Framework::refresh_opener($msg, 'emailadmin', $content['acc_id'], $new_account ? 'add' : 'update', null, null, null, $msg_type); |
|
1126
|
|
|
if ($button == 'save') Framework::window_close(); |
|
1127
|
|
|
break; |
|
1128
|
|
|
|
|
1129
|
|
|
case 'delete': |
|
1130
|
|
|
if (!Mail\Account::check_access(Acl::DELETE, $content)) |
|
1131
|
|
|
{ |
|
1132
|
|
|
$msg = lang('Permission denied!'); |
|
1133
|
|
|
$msg_type = 'error'; |
|
1134
|
|
|
} |
|
1135
|
|
|
elseif (Mail\Account::delete($content['acc_id']) > 0) |
|
1136
|
|
|
{ |
|
1137
|
|
|
if ($content['acc_id']) Mail::unsetCachedObjects($content['acc_id']); |
|
1138
|
|
|
Framework::refresh_opener(lang('Account deleted.'), 'emailadmin', $content['acc_id'], 'delete'); |
|
1139
|
|
|
Framework::window_close(); |
|
1140
|
|
|
} |
|
1141
|
|
|
else |
|
1142
|
|
|
{ |
|
1143
|
|
|
$msg = lang('Failed to delete account!'); |
|
1144
|
|
|
$msg_type = 'error'; |
|
1145
|
|
|
} |
|
1146
|
|
|
} |
|
1147
|
|
|
} |
|
1148
|
|
|
// SMIME UPLOAD/DELETE/EXPORT control |
|
1149
|
|
|
$content['hide_smime_upload'] = false; |
|
1150
|
|
|
if (!empty($content['acc_smime_cred_id'])) |
|
1151
|
|
|
{ |
|
1152
|
|
|
if (!empty($content['smime_delete_p12']) && |
|
1153
|
|
|
Mail\Credentials::delete ( |
|
1154
|
|
|
$content['acc_id'], |
|
1155
|
|
|
$content['called_for'] ? $content['called_for'] : $GLOBALS['egw_info']['user']['account_id'], |
|
1156
|
|
|
Mail\Credentials::SMIME |
|
1157
|
|
|
)) |
|
1158
|
|
|
{ |
|
1159
|
|
|
unset($content['acc_smime_password'], $content['smimeKeyUpload'], $content['smime_delete_p12'], $content['acc_smime_cred_id']); |
|
1160
|
|
|
$content['hide_smime_upload'] = false; |
|
1161
|
|
|
} |
|
1162
|
|
|
else |
|
1163
|
|
|
{ |
|
1164
|
|
|
// do NOT send smime private key to client side, it's unnecessary and binary blob breaks json encoding |
|
1165
|
|
|
$content['acc_smime_password'] = Mail\Credentials::UNAVAILABLE; |
|
1166
|
|
|
|
|
1167
|
|
|
$content['hide_smime_upload'] = true; |
|
1168
|
|
|
} |
|
1169
|
|
|
} |
|
1170
|
|
|
|
|
1171
|
|
|
// disable delete button for new, not yet saved entries, if no delete rights or a non-standard identity selected |
|
1172
|
|
|
$readonlys['button[delete]'] = empty($content['acc_id']) || |
|
1173
|
|
|
!Mail\Account::check_access(Acl::DELETE, $content) || |
|
1174
|
|
|
$content['ident_id'] != $content['std_ident_id']; |
|
1175
|
|
|
|
|
1176
|
|
|
// if account is for multiple user, change delete confirmation to reflect that |
|
1177
|
|
|
if (Mail\Account::is_multiple($content)) |
|
1178
|
|
|
{ |
|
1179
|
|
|
$tpl->setElementAttribute('button[delete]', 'onclick', "et2_dialog.confirm(widget,'This is NOT a personal mail account!\\n\\nAccount will be deleted for ALL users!\\n\\nAre you really sure you want to do that?','Delete this account')"); |
|
1180
|
|
|
} |
|
1181
|
|
|
|
|
1182
|
|
|
// if no edit access, make whole dialog readonly |
|
1183
|
|
|
if (!$edit_access) |
|
1184
|
|
|
{ |
|
1185
|
|
|
$readonlys['__ALL__'] = true; |
|
1186
|
|
|
$readonlys['button[cancel]'] = false; |
|
1187
|
|
|
// allow to edit notification-folders |
|
1188
|
|
|
$readonlys['button[save]'] = $readonlys['button[apply]'] = |
|
1189
|
|
|
$readonlys['notify_folders'] = $readonlys['notify_use_default'] = false; |
|
1190
|
|
|
// allow to edit sMime stuff |
|
1191
|
|
|
$readonlys['smimeGenerate'] = $readonlys['smimeKeyUpload'] = $readonlys['smime_pkcs12_password'] = |
|
1192
|
|
|
$readonlys['smime_export_p12'] = $readonlys['smime_delete_p12'] = false; |
|
1193
|
|
|
} |
|
1194
|
|
|
|
|
1195
|
|
|
$sel_options['acc_imap_ssl'] = $sel_options['acc_sieve_ssl'] = |
|
1196
|
|
|
$sel_options['acc_smtp_ssl'] = self::$ssl_types; |
|
|
|
|
|
|
1197
|
|
|
|
|
1198
|
|
|
// admin access to account with no credentials available |
|
1199
|
|
|
if ($this->is_admin && (empty($content['acc_imap_username']) || empty($content['acc_imap_host']) || $content['called_for'])) |
|
1200
|
|
|
{ |
|
1201
|
|
|
// cant connection to imap --> allow free entries in taglists |
|
1202
|
|
|
foreach(array('acc_folder_sent', 'acc_folder_trash', 'acc_folder_draft', 'acc_folder_template', 'acc_folder_junk') as $folder) |
|
1203
|
|
|
{ |
|
1204
|
|
|
$tpl->setElementAttribute($folder, 'allowFreeEntries', true); |
|
1205
|
|
|
} |
|
1206
|
|
|
} |
|
1207
|
|
|
else |
|
1208
|
|
|
{ |
|
1209
|
|
|
try { |
|
1210
|
|
|
$sel_options['acc_folder_sent'] = $sel_options['acc_folder_trash'] = |
|
1211
|
|
|
$sel_options['acc_folder_draft'] = $sel_options['acc_folder_template'] = |
|
1212
|
|
|
$sel_options['acc_folder_junk'] = $sel_options['acc_folder_archive'] = |
|
1213
|
|
|
$sel_options['notify_folders'] = $sel_options['acc_folder_ham'] = |
|
1214
|
|
|
self::mailboxes(self::imap_client ($content)); |
|
1215
|
|
|
// Allow folder notification on INBOX for popup_only |
|
1216
|
|
|
if ($GLOBALS['egw_info']['user']['preferences']['notifications']['notification_chain'] == 'popup_only') |
|
1217
|
|
|
{ |
|
1218
|
|
|
$sel_options['notify_folders']['INBOX'] = lang('INBOX'); |
|
1219
|
|
|
} |
|
1220
|
|
|
} |
|
1221
|
|
|
catch(Exception $e) { |
|
1222
|
|
|
if (self::$debug) _egw_log_exception($e); |
|
1223
|
|
|
// let user know what the problem is and that he can fix it using wizard or deleting |
|
1224
|
|
|
$msg = lang($e->getMessage())."\n\n".lang('You can use wizard to fix account settings or delete account.'); |
|
1225
|
|
|
$msg_type = 'error'; |
|
1226
|
|
|
// cant connection to imap --> allow free entries in taglists |
|
1227
|
|
|
foreach(array('acc_folder_sent', 'acc_folder_trash', 'acc_folder_draft', 'acc_folder_template', 'acc_folder_junk') as $folder) |
|
1228
|
|
|
{ |
|
1229
|
|
|
$tpl->setElementAttribute($folder, 'allowFreeEntries', true); |
|
1230
|
|
|
} |
|
1231
|
|
|
} |
|
1232
|
|
|
} |
|
1233
|
|
|
|
|
1234
|
|
|
$sel_options['acc_imap_type'] = Mail\Types::getIMAPServerTypes(false); |
|
1235
|
|
|
$sel_options['acc_smtp_type'] = Mail\Types::getSMTPServerTypes(false); |
|
1236
|
|
|
$sel_options['acc_imap_logintype'] = self::$login_types; |
|
1237
|
|
|
$sel_options['ident_id'] = $content['identities']; |
|
1238
|
|
|
$sel_options['acc_id'] = $content['accounts']; |
|
1239
|
|
|
$sel_options['acc_further_identities'] = self::$further_identities; |
|
1240
|
|
|
|
|
1241
|
|
|
// user is allowed to create or edit further identities |
|
1242
|
|
|
if ($edit_access || $content['acc_further_identities']) |
|
1243
|
|
|
{ |
|
1244
|
|
|
$sel_options['ident_id']['new'] = lang('Create new identity'); |
|
1245
|
|
|
$readonlys['ident_id'] = false; |
|
1246
|
|
|
|
|
1247
|
|
|
// if no edit-access and identity is not standard identity --> allow to edit identity |
|
1248
|
|
|
if (!$edit_access && $content['ident_id'] != $content['std_ident_id']) |
|
1249
|
|
|
{ |
|
1250
|
|
|
$readonlys += array( |
|
1251
|
|
|
'button[save]' => false, 'button[apply]' => false, |
|
1252
|
|
|
'button[placeholders]' => false, |
|
1253
|
|
|
'ident_name' => false, |
|
1254
|
|
|
'ident_realname' => false, 'ident_email' => false, 'ident_email_alias' => false, |
|
1255
|
|
|
'ident_org' => false, 'ident_signature' => false, |
|
1256
|
|
|
); |
|
1257
|
|
|
} |
|
1258
|
|
|
if ($content['ident_id'] != $content['old_ident_id'] && |
|
1259
|
|
|
($content['old_ident_id'] || $content['ident_id'] != $content['std_ident_id'])) |
|
1260
|
|
|
{ |
|
1261
|
|
|
if ($content['ident_id'] > 0) |
|
1262
|
|
|
{ |
|
1263
|
|
|
$identity = Mail\Account::read_identity($content['ident_id'], false, $content['called_for']); |
|
1264
|
|
|
unset($identity['account_id']); |
|
1265
|
|
|
$content = array_merge($content, $identity, array('ident_email_alias' => $identity['ident_email'])); |
|
1266
|
|
|
} |
|
1267
|
|
|
else |
|
1268
|
|
|
{ |
|
1269
|
|
|
$content['ident_name'] = $content['ident_realname'] = $content['ident_email'] = |
|
1270
|
|
|
$content['ident_email_alias'] = $content['ident_org'] = $content['ident_signature'] = ''; |
|
1271
|
|
|
} |
|
1272
|
|
|
if (empty($msg) && $edit_access && $content['ident_id'] && $content['ident_id'] != $content['std_ident_id']) |
|
1273
|
|
|
{ |
|
1274
|
|
|
$msg = lang('Switch back to standard identity to save other account data.'); |
|
1275
|
|
|
$msg_type = 'help'; |
|
1276
|
|
|
} |
|
1277
|
|
|
$content['old_ident_id'] = $content['ident_id']; |
|
1278
|
|
|
} |
|
1279
|
|
|
} |
|
1280
|
|
|
$content['old_acc_id'] = $content['acc_id']; |
|
1281
|
|
|
|
|
1282
|
|
|
// if only aliases are allowed for futher identities, add them as options |
|
1283
|
|
|
// allow admins to always add arbitrary aliases |
|
1284
|
|
|
if ($content['acc_further_identities'] == 2 && !$this->is_admin) |
|
1285
|
|
|
{ |
|
1286
|
|
|
$sel_options['ident_email_alias'] = array_merge( |
|
1287
|
|
|
array('' => $content['mailLocalAddress'].' ('.lang('Default').')'), |
|
1288
|
|
|
array_combine($content['mailAlternateAddress'], $content['mailAlternateAddress'])); |
|
1289
|
|
|
// if admin explicitly set a non-alias, we need to add it to aliases to keep it after storing signature by user |
|
1290
|
|
|
if ($content['ident_email'] !== $content['mailLocalAddress'] && !isset($sel_options['ident_email_alias'][$content['ident_email']])) |
|
1291
|
|
|
{ |
|
1292
|
|
|
$sel_options['ident_email_alias'][$content['ident_email']] = $content['ident_email']; |
|
1293
|
|
|
} |
|
1294
|
|
|
// copy ident_email to select-box ident_email_alias, as et2 requires unique ids |
|
1295
|
|
|
$content['ident_email_alias'] = $content['ident_email']; |
|
1296
|
|
|
$content['select_ident_mail'] = true; |
|
1297
|
|
|
} |
|
1298
|
|
|
|
|
1299
|
|
|
// only allow to delete further identities, not a standard identity |
|
1300
|
|
|
$readonlys['button[delete_identity]'] = !($content['ident_id'] > 0 && $content['ident_id'] != $content['std_ident_id']); |
|
1301
|
|
|
|
|
1302
|
|
|
// disable aliases tab for default smtp class EGroupware\Api\Mail\Smtp |
|
1303
|
|
|
$readonlys['tabs']['admin.mailaccount.aliases'] = !$content['acc_smtp_type'] || |
|
1304
|
|
|
$content['acc_smtp_type'] == 'EGroupware\\Api\\Mail\\Smtp'; |
|
1305
|
|
|
if ($readonlys['tabs']['admin.mailaccount.aliases']) |
|
1306
|
|
|
{ |
|
1307
|
|
|
unset($sel_options['acc_further_identities'][2]); // can limit identities to aliases without aliases ;-) |
|
1308
|
|
|
} |
|
1309
|
|
|
|
|
1310
|
|
|
// allow smtp class to disable certain features in alias tab |
|
1311
|
|
|
if ($content['acc_smtp_type'] && class_exists($content['acc_smtp_type']) && |
|
1312
|
|
|
is_a($content['acc_smtp_type'], 'EGroupware\\Api\\Mail\\Smtp\\Ldap', true)) |
|
1313
|
|
|
{ |
|
1314
|
|
|
$content['no_forward_available'] = !constant($content['acc_smtp_type'].'::FORWARD_ATTR'); |
|
1315
|
|
|
if (!constant($content['acc_smtp_type'].'::FORWARD_ONLY_ATTR')) |
|
1316
|
|
|
{ |
|
1317
|
|
|
$readonlys['deliveryMode'] = true; |
|
1318
|
|
|
} |
|
1319
|
|
|
} |
|
1320
|
|
|
|
|
1321
|
|
|
// account allows users to change forwards |
|
1322
|
|
|
if (!$edit_access && !$readonlys['tabs']['admin.mailaccount.aliases'] && $content['acc_user_forward']) |
|
1323
|
|
|
{ |
|
1324
|
|
|
$readonlys['mailForwardingAddress'] = false; |
|
1325
|
|
|
} |
|
1326
|
|
|
|
|
1327
|
|
|
// allow imap classes to disable certain tabs or fields |
|
1328
|
|
|
if (($class = Mail\Account::getIcClass($content['acc_imap_type'])) && class_exists($class) && |
|
1329
|
|
|
($imap_ro = call_user_func(array($class, 'getUIreadonlys')))) |
|
1330
|
|
|
{ |
|
1331
|
|
|
$readonlys = array_merge($readonlys, $imap_ro, array( |
|
1332
|
|
|
'tabs' => array_merge((array)$readonlys['tabs'], (array)$imap_ro['tabs']), |
|
1333
|
|
|
)); |
|
1334
|
|
|
} |
|
1335
|
|
|
Framework::message($msg ? $msg : (string)$_GET['msg'], $msg_type); |
|
1336
|
|
|
|
|
1337
|
|
|
if (is_array($content['account_id']) && count($content['account_id']) > 1) |
|
1338
|
|
|
{ |
|
1339
|
|
|
$tpl->setElementAttribute('account_id', 'multiple', true); |
|
1340
|
|
|
$readonlys['button[multiple]'] = true; |
|
1341
|
|
|
} |
|
1342
|
|
|
// when called by admin for existing accounts, display further administrative actions |
|
1343
|
|
|
if ($content['called_for'] && $content['acc_id'] > 0) |
|
1344
|
|
|
{ |
|
1345
|
|
|
$admin_actions = array(); |
|
1346
|
|
|
foreach(Api\Hooks::process(array( |
|
1347
|
|
|
'location' => 'emailadmin_edit', |
|
1348
|
|
|
'account_id' => $content['called_for'], |
|
1349
|
|
|
'acc_id' => $content['acc_id'], |
|
1350
|
|
|
)) as $actions) |
|
1351
|
|
|
{ |
|
1352
|
|
|
if ($actions) $admin_actions = array_merge($admin_actions, $actions); |
|
1353
|
|
|
} |
|
1354
|
|
|
if ($admin_actions) $tpl->setElementAttribute('admin_actions', 'actions', $admin_actions); |
|
1355
|
|
|
} |
|
1356
|
|
|
$content['admin_actions'] = (bool)$admin_actions; |
|
1357
|
|
|
|
|
1358
|
|
|
//try to fix identities with no domain part set e.g. alias as identity |
|
1359
|
|
|
if (!strpos($content['ident_email'], '@')) |
|
1360
|
|
|
{ |
|
1361
|
|
|
$content['ident_email'] = Mail::fixInvalidAliasAddress (Api\Accounts::id2name($content['acc_imap_account_id'], 'account_email'), $content['ident_email']); |
|
1362
|
|
|
} |
|
1363
|
|
|
|
|
1364
|
|
|
$tpl->exec(static::APP_CLASS.'edit', $content, $sel_options, $readonlys, $content, 2); |
|
1365
|
|
|
} |
|
1366
|
|
|
|
|
1367
|
|
|
/** |
|
1368
|
|
|
* Saves the smime key |
|
1369
|
|
|
* |
|
1370
|
|
|
* @param array $content |
|
1371
|
|
|
* @param Etemplate $tpl |
|
1372
|
|
|
* @param int $account_id =null account to save smime key for, default current user |
|
1373
|
|
|
* @return int cred_id or null on error |
|
1374
|
|
|
*/ |
|
1375
|
|
|
private static function save_smime_key(array $content, Etemplate $tpl, $account_id=null) |
|
1376
|
|
|
{ |
|
1377
|
|
|
if (($pkcs12 = file_get_contents($content['smimeKeyUpload']['tmp_name']))) |
|
1378
|
|
|
{ |
|
1379
|
|
|
$cert_info = Mail\Smime::extractCertPKCS12($pkcs12, $content['smime_pkcs12_password']); |
|
1380
|
|
|
if (is_array($cert_info) && !empty($cert_info['cert'])) |
|
|
|
|
|
|
1381
|
|
|
{ |
|
1382
|
|
|
// save public key |
|
1383
|
|
|
$smime = new Mail\Smime; |
|
1384
|
|
|
$email = $smime->getEmailFromKey($cert_info['cert']); |
|
1385
|
|
|
$AB_bo = new addressbook_bo(); |
|
1386
|
|
|
$AB_bo->set_smime_keys(array( |
|
1387
|
|
|
$email => $cert_info['cert'] |
|
1388
|
|
|
)); |
|
1389
|
|
|
// save private key |
|
1390
|
|
|
if (!isset($account_id)) $account_id = $GLOBALS['egw_info']['user']['account_id']; |
|
1391
|
|
|
return Mail\Credentials::write($content['acc_id'], $email, $pkcs12, Mail\Credentials::SMIME, $account_id); |
|
1392
|
|
|
} |
|
1393
|
|
|
$tpl->set_validation_error('smimeKeyUpload', lang('Could not extract private key from given p12 file. Either the p12 file is broken or password is wrong!')); |
|
1394
|
|
|
} |
|
1395
|
|
|
return null; |
|
1396
|
|
|
} |
|
1397
|
|
|
|
|
1398
|
|
|
/** |
|
1399
|
|
|
* Replace 0 with '' or back |
|
1400
|
|
|
* |
|
1401
|
|
|
* @param string|array &$account_id on return always array |
|
1402
|
|
|
* @param boolean $back =false |
|
1403
|
|
|
*/ |
|
1404
|
|
|
private static function fix_account_id_0(&$account_id=null, $back=false) |
|
1405
|
|
|
{ |
|
1406
|
|
|
if (!isset($account_id)) return; |
|
1407
|
|
|
|
|
1408
|
|
|
if (!is_array($account_id)) |
|
1409
|
|
|
{ |
|
1410
|
|
|
$account_id = explode(',', $account_id); |
|
1411
|
|
|
} |
|
1412
|
|
|
if (($k = array_search($back?'':'0', $account_id)) !== false) |
|
1413
|
|
|
{ |
|
1414
|
|
|
$account_id[$k] = $back ? '0' : ''; |
|
1415
|
|
|
} |
|
1416
|
|
|
} |
|
1417
|
|
|
|
|
1418
|
|
|
/** |
|
1419
|
|
|
* Instanciate imap-client |
|
1420
|
|
|
* |
|
1421
|
|
|
* @param array $content |
|
1422
|
|
|
* @param int $timeout =null default use value returned by Mail\Imap::getTimeOut() |
|
1423
|
|
|
* @return Horde_Imap_Client_Socket |
|
1424
|
|
|
*/ |
|
1425
|
|
|
protected static function imap_client(array $content, $timeout=null) |
|
1426
|
|
|
{ |
|
1427
|
|
|
return new Horde_Imap_Client_Socket(array( |
|
1428
|
|
|
'username' => $content['acc_imap_username'], |
|
1429
|
|
|
'password' => $content['acc_imap_password'], |
|
1430
|
|
|
'hostspec' => $content['acc_imap_host'], |
|
1431
|
|
|
'port' => $content['acc_imap_port'], |
|
1432
|
|
|
'secure' => self::$ssl2secure[(string)array_search($content['acc_imap_ssl'], self::$ssl2type)], |
|
1433
|
|
|
'timeout' => $timeout > 0 ? $timeout : Mail\Imap::getTimeOut(), |
|
1434
|
|
|
'debug' => self::DEBUG_LOG, |
|
1435
|
|
|
)); |
|
1436
|
|
|
} |
|
1437
|
|
|
|
|
1438
|
|
|
/** |
|
1439
|
|
|
* Reorder SSL types to make sure we start with TLS, SSL, STARTTLS and insecure last |
|
1440
|
|
|
* |
|
1441
|
|
|
* @param array $data ssl => port pairs plus other data like value for 'username' |
|
1442
|
|
|
* @return array |
|
1443
|
|
|
*/ |
|
1444
|
|
|
protected static function fix_ssl_order($data) |
|
1445
|
|
|
{ |
|
1446
|
|
|
$ordered = array(); |
|
1447
|
|
|
foreach(array_merge(array('TLS', 'SSL', 'STARTTLS'), array_keys($data)) as $key) |
|
1448
|
|
|
{ |
|
1449
|
|
|
if (array_key_exists($key, $data)) $ordered[$key] = $data[$key]; |
|
1450
|
|
|
} |
|
1451
|
|
|
return $ordered; |
|
1452
|
|
|
} |
|
1453
|
|
|
|
|
1454
|
|
|
/** |
|
1455
|
|
|
* Query Mozilla's ISPDB |
|
1456
|
|
|
* |
|
1457
|
|
|
* Some providers eg. 1-and-1 do not report their hosted domains to ISPDB, |
|
1458
|
|
|
* therefore we try it with the found MX and it's domain-part (host-name removed). |
|
1459
|
|
|
* |
|
1460
|
|
|
* @param string $domain domain or email |
|
1461
|
|
|
* @param boolean $try_mx =true if domain itself is not found, try mx or domain-part (host removed) of mx |
|
1462
|
|
|
* @return array with values for keys 'displayName', 'imap', 'smtp', 'pop3', which each contain |
|
1463
|
|
|
* array of arrays with values for keys 'hostname', 'port', 'socketType'=(SSL|STARTTLS), 'username'=%EMAILADDRESS% |
|
1464
|
|
|
*/ |
|
1465
|
|
|
protected static function mozilla_ispdb($domain, $try_mx=true) |
|
1466
|
|
|
{ |
|
1467
|
|
|
if (strpos($domain, '@') !== false) list(,$domain) = explode('@', $domain); |
|
1468
|
|
|
|
|
1469
|
|
|
$url = 'https://autoconfig.thunderbird.net/v1.1/'.$domain; |
|
1470
|
|
|
try { |
|
1471
|
|
|
$xml = @simplexml_load_file($url); |
|
1472
|
|
|
if (!$xml->emailProvider) throw new Api\Exception\NotFound(); |
|
1473
|
|
|
$provider = array( |
|
1474
|
|
|
'displayName' => (string)$xml->emailProvider->displayName, |
|
1475
|
|
|
); |
|
1476
|
|
|
foreach($xml->emailProvider->children() as $tag => $server) |
|
1477
|
|
|
{ |
|
1478
|
|
|
if (!in_array($tag, array('incomingServer', 'outgoingServer'))) continue; |
|
1479
|
|
|
foreach($server->attributes() as $name => $value) |
|
1480
|
|
|
{ |
|
1481
|
|
|
if ($name == 'type') $type = (string)$value; |
|
1482
|
|
|
} |
|
1483
|
|
|
$data = array(); |
|
1484
|
|
|
foreach($server as $name => $value) |
|
1485
|
|
|
{ |
|
1486
|
|
|
foreach($value->children() as $tag => $val) |
|
|
|
|
|
|
1487
|
|
|
{ |
|
1488
|
|
|
$data[$name][$tag] = (string)$val; |
|
1489
|
|
|
} |
|
1490
|
|
|
if (!isset($data[$name])) $data[$name] = (string)$value; |
|
1491
|
|
|
} |
|
1492
|
|
|
$provider[$type][] = $data; |
|
1493
|
|
|
} |
|
1494
|
|
|
} |
|
1495
|
|
|
catch(Exception $e) { |
|
1496
|
|
|
// ignore own not-found exception or xml parsing execptions |
|
1497
|
|
|
unset($e); |
|
1498
|
|
|
|
|
1499
|
|
|
if ($try_mx && ($dns = dns_get_record($domain, DNS_MX))) |
|
1500
|
|
|
{ |
|
1501
|
|
|
$domain = $dns[0]['target']; |
|
1502
|
|
|
if (!($provider = self::mozilla_ispdb($domain, false))) |
|
1503
|
|
|
{ |
|
1504
|
|
|
list(,$domain) = explode('.', $domain, 2); |
|
1505
|
|
|
$provider = self::mozilla_ispdb($domain, false); |
|
1506
|
|
|
} |
|
1507
|
|
|
} |
|
1508
|
|
|
else |
|
1509
|
|
|
{ |
|
1510
|
|
|
$provider = array(); |
|
1511
|
|
|
} |
|
1512
|
|
|
} |
|
1513
|
|
|
//error_log(__METHOD__."('$email') returning ".array2string($provider)); |
|
1514
|
|
|
return $provider; |
|
1515
|
|
|
} |
|
1516
|
|
|
|
|
1517
|
|
|
/** |
|
1518
|
|
|
* Guess possible server hostnames from email address: |
|
1519
|
|
|
* - $type.$domain, mail.$domain |
|
1520
|
|
|
* - replace host in MX with imap or mail |
|
1521
|
|
|
* - MX for $domain |
|
1522
|
|
|
* |
|
1523
|
|
|
* @param string $email email address |
|
1524
|
|
|
* @param string $type ='imap' 'imap' or 'smtp', used as hostname beside 'mail' |
|
1525
|
|
|
* @return array of hostname => true pairs |
|
1526
|
|
|
*/ |
|
1527
|
|
|
protected function guess_hosts($email, $type='imap') |
|
1528
|
|
|
{ |
|
1529
|
|
|
list(,$domain) = explode('@', $email); |
|
1530
|
|
|
|
|
1531
|
|
|
$hosts = array(); |
|
1532
|
|
|
|
|
1533
|
|
|
// try usuall names |
|
1534
|
|
|
$hosts[$type.'.'.$domain] = true; |
|
1535
|
|
|
$hosts['mail.'.$domain] = true; |
|
1536
|
|
|
if ($type == 'smtp') $hosts['send.'.$domain] = true; |
|
1537
|
|
|
|
|
1538
|
|
|
if (($dns = dns_get_record($domain, DNS_MX))) |
|
1539
|
|
|
{ |
|
1540
|
|
|
//error_log(__METHOD__."('$email') dns_get_record('$domain', DNS_MX) returned ".array2string($dns)); |
|
1541
|
|
|
// hosts for office365 are outlook|smpt.office365.com for MX *.mail.protection.outlook.com |
|
1542
|
|
|
if (substr($dns[0]['target'], -28) == '.mail.protection.outlook.com') |
|
1543
|
|
|
{ |
|
1544
|
|
|
$hosts[($type == 'imap' ? 'outlook' : 'smtp').'.office365.com'] = true; |
|
1545
|
|
|
} |
|
1546
|
|
|
$hosts[preg_replace('/^[^.]+/', $type, $dns[0]['target'])] = true; |
|
1547
|
|
|
$hosts[preg_replace('/^[^.]+/', 'mail', $dns[0]['target'])] = true; |
|
1548
|
|
|
if ($type == 'smtp') $hosts[preg_replace('/^[^.]+/', 'send', $dns[0]['target'])] = true; |
|
1549
|
|
|
$hosts[$dns[0]['target']] = true; |
|
1550
|
|
|
} |
|
1551
|
|
|
|
|
1552
|
|
|
// verify hosts in dns |
|
1553
|
|
|
foreach(array_keys($hosts) as $host) |
|
1554
|
|
|
{ |
|
1555
|
|
|
if (!dns_get_record($host, DNS_A)) unset($hosts[$host]); |
|
1556
|
|
|
} |
|
1557
|
|
|
//error_log(__METHOD__."('$email') returning ".array2string($hosts)); |
|
1558
|
|
|
return $hosts; |
|
1559
|
|
|
} |
|
1560
|
|
|
|
|
1561
|
|
|
/** |
|
1562
|
|
|
* Set mail account status wheter to 'active' or '' (inactive) |
|
1563
|
|
|
* |
|
1564
|
|
|
* @param array $_data account an array of data called via long task running dialog |
|
1565
|
|
|
* $_data:array ( |
|
1566
|
|
|
* id => account_id, |
|
1567
|
|
|
* qouta => quotaLimit, |
|
1568
|
|
|
* domain => mailLocalAddress, |
|
1569
|
|
|
* status => mail activation status('active'|'') |
|
1570
|
|
|
* ) |
|
1571
|
|
|
* @param string $etemplate_exec_id to check against CSRF |
|
1572
|
|
|
* @return json response |
|
1573
|
|
|
*/ |
|
1574
|
|
|
public function ajax_activeAccounts($_data, $etemplate_exec_id) |
|
1575
|
|
|
{ |
|
1576
|
|
|
Api\Etemplate\Request::csrfCheck($etemplate_exec_id, __METHOD__, func_get_args()); |
|
1577
|
|
|
|
|
1578
|
|
|
if (!$this->is_admin) die('no rights to be here!'); |
|
|
|
|
|
|
1579
|
|
|
$response = Api\Json\Response::get(); |
|
1580
|
|
|
if (($account = $GLOBALS['egw']->accounts->read($_data['id']))) |
|
1581
|
|
|
{ |
|
1582
|
|
|
if ($_data['quota'] !== '' || $_data['accountStatus'] !== '' |
|
1583
|
|
|
|| strpos($_data['domain'], '.')) |
|
1584
|
|
|
{ |
|
1585
|
|
|
$emailadmin = Mail\Account::get_default(); |
|
1586
|
|
|
if (!Mail\Account::is_multiple($emailadmin)) |
|
1587
|
|
|
{ |
|
1588
|
|
|
$msg = lang('No default account found!'); |
|
1589
|
|
|
return $response->data($msg); |
|
1590
|
|
|
} |
|
1591
|
|
|
|
|
1592
|
|
|
$ea_account = Mail\Account::read($emailadmin->acc_id, $_data['id']); |
|
1593
|
|
|
if (($userData = $ea_account->getUserData ())) |
|
1594
|
|
|
{ |
|
1595
|
|
|
$userData = array( |
|
1596
|
|
|
'acc_smtp_type' => $ea_account->acc_smtp_type, |
|
1597
|
|
|
'accountStatus' => $_data['status'], |
|
1598
|
|
|
'quotaLimit' => $_data['qouta']? $_data['qouta']: $userData['qoutaLimit'], |
|
1599
|
|
|
'mailLocalAddress' => $userData['mailLocalAddress'] |
|
1600
|
|
|
); |
|
1601
|
|
|
|
|
1602
|
|
|
if (strpos($_data['domain'], '.') !== false) |
|
1603
|
|
|
{ |
|
1604
|
|
|
$userData['mailLocalAddress'] = preg_replace('/@'.preg_quote($ea_account->acc_domain).'$/', '@'.$_data['domain'], $userData['mailLocalAddress']); |
|
1605
|
|
|
|
|
1606
|
|
|
foreach($userData['mailAlternateAddress'] as &$alias) |
|
1607
|
|
|
{ |
|
1608
|
|
|
$alias = preg_replace('/@'.preg_quote($ea_account->acc_domain).'$/', '@'.$_data['domain'], $alias); |
|
1609
|
|
|
} |
|
1610
|
|
|
} |
|
1611
|
|
|
// fullfill the saveUserData requirements |
|
1612
|
|
|
$userData += $ea_account->params; |
|
1613
|
|
|
$ea_account->saveUserData($_data['id'], $userData); |
|
1614
|
|
|
$msg = '#'.$_data['id'].' '.$account['account_fullname']. ' '.($userData['accountStatus'] == 'active'? lang('activated'):lang('deactivated')); |
|
1615
|
|
|
} |
|
1616
|
|
|
else |
|
1617
|
|
|
{ |
|
1618
|
|
|
$msg .= lang('No profile defined for user %1', '#'.$_data['id'].' '.$account['account_fullname']."\n"); |
|
|
|
|
|
|
1619
|
|
|
|
|
1620
|
|
|
} |
|
1621
|
|
|
} |
|
1622
|
|
|
} |
|
1623
|
|
|
$response->data($msg); |
|
1624
|
|
|
} |
|
1625
|
|
|
} |
|
1626
|
|
|
|
|
1627
|
|
|
/** |
|
1628
|
|
|
* Trivial file logger, as Horde\ManageSieve does not support just a file |
|
1629
|
|
|
*/ |
|
1630
|
|
|
class admin_mail_logger |
|
1631
|
|
|
{ |
|
1632
|
|
|
private $fp; |
|
1633
|
|
|
|
|
1634
|
|
|
public function __construct($log) |
|
1635
|
|
|
{ |
|
1636
|
|
|
$this->fp = is_resource($log) ? $log : fopen($log, 'a'); |
|
1637
|
|
|
} |
|
1638
|
|
|
|
|
1639
|
|
|
public function debug($msg) |
|
1640
|
|
|
{ |
|
1641
|
|
|
fwrite($this->fp, $msg."\n"); |
|
1642
|
|
|
} |
|
1643
|
|
|
} |
|
1644
|
|
|
|
Let?s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let?s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: