1<?php
2/*********************************************************************
3    class.email.php
4
5    Peter Rotich <peter@osticket.com>
6    Copyright (c)  2006-2013 osTicket
7    http://www.osticket.com
8
9    Released under the GNU General Public License WITHOUT ANY WARRANTY.
10    See LICENSE.TXT for details.
11
12    vim: expandtab sw=4 ts=4 sts=4:
13**********************************************************************/
14include_once INCLUDE_DIR.'class.role.php';
15include_once(INCLUDE_DIR.'class.dept.php');
16include_once(INCLUDE_DIR.'class.mailfetch.php');
17
18class Email extends VerySimpleModel {
19    static $meta = array(
20        'table' => EMAIL_TABLE,
21        'pk' => array('email_id'),
22        'joins' => array(
23            'priority' => array(
24                'constraint' => array('priority_id' => 'Priority.priority_id'),
25                'null' => true,
26            ),
27            'dept' => array(
28                'constraint' => array('dept_id' => 'Dept.id'),
29                'null' => true,
30            ),
31            'topic' => array(
32                'constraint' => array('topic_id' => 'Topic.topic_id'),
33                'null' => true,
34            ),
35        )
36    );
37
38    const PERM_BANLIST = 'emails.banlist';
39
40    static protected $perms = array(
41            self::PERM_BANLIST => array(
42                'title' =>
43                /* @trans */ 'Banlist',
44                'desc'  =>
45                /* @trans */ 'Ability to add/remove emails from banlist via ticket interface',
46                'primary' => true,
47            ));
48
49
50    var $address;
51    var $mail_proto;
52
53    function getId() {
54        return $this->email_id;
55    }
56
57    function __toString() {
58        if ($this->name)
59            return sprintf('%s <%s>', $this->name, $this->email);
60
61        return $this->email;
62    }
63
64
65    function __onload() {
66        $this->mail_proto = $this->get('mail_protocol');
67        if ($this->mail_encryption == 'SSL')
68            $this->mail_proto .= "/".$this->mail_encryption;
69
70        $this->address=$this->name?($this->name.'<'.$this->email.'>'):$this->email;
71    }
72
73    function getEmail() {
74        return $this->email;
75    }
76
77    function getAddress() {
78        return $this->address;
79    }
80
81    function getName() {
82        return $this->name;
83    }
84
85    function getPriorityId() {
86        return $this->priority_id;
87    }
88
89    function getDeptId() {
90        return $this->dept_id;
91    }
92
93    function getDept() {
94        return $this->dept;
95    }
96
97    function getTopicId() {
98        return $this->topic_id;
99    }
100
101    function getTopic() {
102        return $this->topic;
103    }
104
105    function autoRespond() {
106        return !$this->noautoresp;
107    }
108
109    function getPasswd() {
110        if (!$this->userpass)
111            return '';
112        return Crypto::decrypt($this->userpass, SECRET_SALT, $this->userid);
113    }
114
115    function getSMTPPasswd() {
116        if (!$this->smtp_userpass)
117            return '';
118        return Crypto::decrypt($this->smtp_userpass, SECRET_SALT, $this->smtp_userid);
119    }
120
121    function getHashtable() {
122        return $this->ht;
123    }
124
125    function getInfo() {
126        $base = $this->getHashtable();
127        $base['mail_proto'] = $this->mail_protocol;
128        if ($this->mail_encryption != 'NONE')
129          $base['mail_proto'] .= "/{$this->mail_encryption}";
130        return $base;
131    }
132
133    function getMailAccountInfo() {
134
135        /*NOTE: Do not change any of the tags - otherwise mail fetching will fail */
136        $info = array(
137                //Mail server info
138                'host'  => $this->mail_host,
139                'port'  => $this->mail_port,
140                'protocol'  => $this->mail_protocol,
141                'encryption' => $this->mail_encryption,
142                'username'  => $this->userid,
143                'password' => Crypto::decrypt($this->userpass, SECRET_SALT, $this->userid),
144                //osTicket specific
145                'email_id'  => $this->getId(), //Required for email routing to work.
146                'max_fetch' => $this->mail_fetchmax,
147                'folder' => $this->mail_folder,
148                'delete_mail' => $this->mail_delete,
149                'archive_folder' => $this->mail_archivefolder
150        );
151
152        return $info;
153    }
154
155    function isSMTPEnabled() {
156
157        return (
158                $this->smtp_active
159                    && ($info=$this->getSMTPInfo())
160                    && (!$info['auth'] || $info['password'])
161                );
162    }
163
164    function allowSpoofing() {
165        return ($this->smtp_spoofing);
166    }
167
168    function getSMTPInfo() {
169        $smtpcreds = $this->smtp_auth_creds;
170        $username = $smtpcreds ? $this->smtp_userid : $this->userid;
171        $passwd = $smtpcreds ? $this->smtp_userpass : $this->userpass;
172
173        $info = array (
174                'host' => $this->smtp_host,
175                'port' => $this->smtp_port,
176                'auth' => (bool) $this->smtp_auth,
177                'username' => $username,
178                'password' => Crypto::decrypt($passwd, SECRET_SALT, $username)
179                );
180
181        return $info;
182    }
183
184    function send($to, $subject, $message, $attachments=null, $options=null, $cc=array()) {
185
186        $mailer = new Mailer($this);
187        if($attachments)
188            $mailer->addAttachments($attachments);
189
190        return $mailer->send($to, $subject, $message, $options, $cc);
191    }
192
193    function sendAutoReply($to, $subject, $message, $attachments=null, $options=array()) {
194        $options+= array('autoreply' => true);
195        return $this->send($to, $subject, $message, $attachments, $options);
196    }
197
198    function sendAlert($to, $subject, $message, $attachments=null, $options=array()) {
199        $options+= array('notice' => true);
200        return $this->send($to, $subject, $message, $attachments, $options);
201    }
202
203   function delete() {
204        global $cfg;
205        //Make sure we are not trying to delete default emails.
206        if(!$cfg || $this->getId()==$cfg->getDefaultEmailId() || $this->getId()==$cfg->getAlertEmailId()) //double...double check.
207            return 0;
208
209        if (!parent::delete())
210            return false;
211
212        $type = array('type' => 'deleted');
213        Signal::send('object.deleted', $this, $type);
214
215        Dept::objects()
216            ->filter(array('email_id' => $this->getId()))
217            ->update(array(
218                'email_id' => $cfg->getDefaultEmailId()
219            ));
220
221        Dept::objects()
222            ->filter(array('autoresp_email_id' => $this->getId()))
223            ->update(array(
224                'autoresp_email_id' => 0,
225            ));
226
227        return true;
228    }
229
230
231    /******* Static functions ************/
232
233   static function getIdByEmail($email) {
234        $qs = static::objects()->filter(Q::any(array(
235                        'email'  => $email,
236                        'userid' => $email
237                        )))
238            ->values_flat('email_id');
239
240        $row = $qs->first();
241        return $row ? $row[0] : false;
242    }
243
244    static function create($vars=false) {
245        $inst = new static($vars);
246        $inst->created = SqlFunction::NOW();
247        return $inst;
248    }
249
250    function save($refetch=false) {
251        if ($this->dirty)
252            $this->updated = SqlFunction::NOW();
253        return parent::save($refetch || $this->dirty);
254    }
255
256    function update($vars, &$errors=false) {
257        global $cfg;
258
259        // very basic checks
260        $vars['cpasswd']=$this->getPasswd(); //Current decrypted password.
261        $vars['smtp_cpasswd']=$this->getSMTPPasswd(); // Current decrypted SMTP password.
262        $vars['name']=Format::striptags(trim($vars['name']));
263        $vars['email']=trim($vars['email']);
264        $vars['mail_folder']=Format::striptags(trim($vars['mail_folder']));
265
266        $id = isset($this->email_id) ? $this->getId() : 0;
267        if($id && $id!=$vars['id'])
268            $errors['err']=__('Get technical help!')
269                .' '.__('Internal error occurred');
270
271        if(!$vars['email'] || !Validator::is_email($vars['email'])) {
272            $errors['email']=__('Valid email required');
273        }elseif(($eid=Email::getIdByEmail($vars['email'])) && $eid!=$id) {
274            $errors['email']=__('Email already exists');
275        }elseif($cfg && !strcasecmp($cfg->getAdminEmail(), $vars['email'])) {
276            $errors['email']=__('Email already used as admin email!');
277        }elseif(Staff::getIdByEmail($vars['email'])) { //make sure the email doesn't belong to any of the staff
278            $errors['email']=__('Email in use by an agent');
279        }
280
281        if(!$vars['name'])
282            $errors['name']=__('Email name required');
283
284        $dept = Dept::lookup($vars['dept_id']);
285        if($dept && !$dept->isActive())
286          $errors['dept_id'] = '';
287
288        $topic = Topic::lookup($vars['topic_id']);
289        if($topic && !$topic->isActive())
290          $errors['topic_id'] = '';
291
292        // Validate Credentials
293        if ($vars['mail_active'] || ($vars['smtp_active'] && $vars['smtp_auth']
294                && !$vars['smtp_auth_creds']))
295            $errors = self::validateCredentials($vars['userid'], $vars['passwd'], $id, $errors, false);
296
297        if ($vars['smtp_active'] && $vars['smtp_auth'] && $vars['smtp_auth_creds'])
298            $errors = self::validateCredentials($vars['smtp_userid'], $vars['smtp_passwd'], null, $errors, true);
299
300        list($vars['mail_protocol'], $encryption) = explode('/', $vars['mail_proto']);
301        $vars['mail_encryption'] = $encryption ?: 'NONE';
302
303        if($vars['mail_active']) {
304            //Check pop/imapinfo only when enabled.
305            if(!function_exists('imap_open'))
306                $errors['mail_active']= __("IMAP doesn't exist. PHP must be compiled with IMAP enabled.");
307            if(!$vars['mail_host'])
308                $errors['mail_host']=__('Host name required');
309            if(!$vars['mail_port'])
310                $errors['mail_port']=__('Port required');
311            if(!$vars['mail_protocol'])
312                $errors['mail_protocol']=__('Select protocol');
313            if(!$vars['mail_fetchfreq'] || !is_numeric($vars['mail_fetchfreq']))
314                $errors['mail_fetchfreq']=__('Fetch interval required');
315            if(!$vars['mail_fetchmax'] || !is_numeric($vars['mail_fetchmax']))
316                $errors['mail_fetchmax']=__('Maximum emails required');
317
318            if($vars['mail_protocol'] == 'POP' && !empty($vars['mail_folder']))
319                $errors['mail_folder'] = __('POP mail servers do not support folders');
320
321            if(!isset($vars['postfetch']))
322                $errors['postfetch']=__('Indicate what to do with fetched emails');
323            elseif(!strcasecmp($vars['postfetch'],'archive')) {
324                if ($vars['mail_protocol'] == 'POP')
325                    $errors['postfetch'] =  __('POP mail servers do not support folders');
326                elseif (!$vars['mail_archivefolder'])
327                    $errors['postfetch'] = __('Valid folder required');
328            }
329        }
330
331        if($vars['smtp_active']) {
332            if(!$vars['smtp_host'])
333                $errors['smtp_host']=__('Host name required');
334            if(!$vars['smtp_port'])
335                $errors['smtp_port']=__('Port required');
336        }
337
338        //abort on errors
339        if ($errors)
340            return false;
341
342        if(!$errors && ($vars['mail_host'] && $vars['userid'])) {
343            $existing = static::objects()
344                ->filter(array(
345                    'mail_host' => $vars['mail_host'],
346                    'userid' => $vars['userid']
347                ));
348
349            if ($id)
350                $existing->exclude(array('email_id' => $id));
351
352            if ($existing->exists())
353                $errors['userid']=$errors['host']=__('Host/userid combination already in use.');
354        }
355
356        $passwd = $vars['passwd'] ?: $vars['cpasswd'];
357        if(!$errors && $vars['mail_active']) {
358            //note: password is unencrypted at this point...MailFetcher expect plain text.
359            $fetcher = new MailFetcher(
360                    array(
361                        'host'  => $vars['mail_host'],
362                        'port'  => $vars['mail_port'],
363                        'folder' => $vars['mail_folder'],
364                        'username'  => $vars['userid'],
365                        'password'  => $passwd,
366                        'protocol'  => $vars['mail_protocol'],
367                        'encryption' => $vars['mail_encryption'])
368                    );
369            if(!$fetcher->connect()) {
370                //$errors['err']='Invalid login. Check '.Format::htmlchars($vars['mail_protocol']).' settings';
371                $errors['err']=sprintf(__('Invalid login. Check %s settings'),Format::htmlchars($vars['mail_protocol']));
372                $errors['mail']='<br>'.$fetcher->getLastError();
373            } elseif ($vars['mail_folder'] && !$fetcher->checkMailbox($vars['mail_folder'],true)) {
374                 $errors['mail_folder']=sprintf(__('Invalid or unknown mail folder! >> %s'),$fetcher->getLastError());
375                 if(!$errors['mail'])
376                     $errors['mail']=__('Invalid or unknown mail folder!');
377            }elseif($vars['mail_archivefolder'] && !$fetcher->checkMailbox($vars['mail_archivefolder'],true)) {
378                 //$errors['postfetch']='Invalid or unknown mail folder! >> '.$fetcher->getLastError().'';
379                 $errors['postfetch']=sprintf(__('Invalid or unknown mail folder! >> %s'),$fetcher->getLastError());
380                 if(!$errors['mail'])
381                     $errors['mail']=__('Invalid or unknown archive folder!');
382            }
383        }
384
385        $smtppasswd = $vars['smtp_passwd'] ?: $vars['smtp_cpasswd'];
386        if(!$errors && $vars['smtp_active']) { //Check SMTP login only.
387            $smtpcreds = $vars['smtp_auth_creds'];
388            require_once 'Mail.php'; // PEAR Mail package
389            $smtp = mail::factory('smtp',
390                    array ('host' => $vars['smtp_host'],
391                           'port' => $vars['smtp_port'],
392                           'auth' => (bool) $vars['smtp_auth'],
393                           'username' => $smtpcreds ? $vars['smtp_userid'] : $vars['userid'],
394                           'password' => $smtpcreds ? $smtppasswd : $passwd,
395                           'timeout'  =>20,
396                           'debug' => false,
397                           ));
398            $mail = $smtp->connect();
399            if(PEAR::isError($mail)) {
400                $errors['err']=__('Unable to log in. Check SMTP settings.');
401                $errors['smtp']='<br>'.$mail->getMessage();
402            }else{
403                $smtp->disconnect(); //Thank you, sir!
404            }
405        }
406
407        if($errors) return false;
408
409        $this->mail_errors = 0;
410        $this->mail_lastfetch = null;
411        $this->email = $vars['email'];
412        $this->name = Format::striptags($vars['name']);
413        $this->dept_id = $vars['dept_id'];
414        $this->priority_id = $vars['priority_id'];
415        $this->topic_id = $vars['topic_id'];
416        $this->noautoresp = $vars['noautoresp'];
417        $this->userid = $vars['userid'];
418        $this->mail_active = $vars['mail_active'];
419        $this->mail_host = $vars['mail_host'];
420        $this->mail_folder = $vars['mail_folder'] ?: null;
421        $this->mail_protocol = $vars['mail_protocol'] ?: 'POP';
422        $this->mail_encryption = $vars['mail_encryption'];
423        $this->mail_port = $vars['mail_port'] ?: 0;
424        $this->mail_fetchfreq = $vars['mail_fetchfreq'] ?: 0;
425        $this->mail_fetchmax = $vars['mail_fetchmax'] ?: 0;
426        $this->smtp_active = $vars['smtp_active'];
427        $this->smtp_host = $vars['smtp_host'];
428        $this->smtp_port = $vars['smtp_port'] ?: 0;
429        $this->smtp_auth = $vars['smtp_auth'];
430        $this->smtp_auth_creds = isset($vars['smtp_auth_creds']) ? 1 : 0;
431        $this->smtp_userid = $vars['smtp_userid'];
432        $this->smtp_spoofing = $vars['smtp_spoofing'];
433        $this->notes = Format::sanitize($vars['notes']);
434
435        //Post fetch email handling...
436        if ($vars['postfetch'] && !strcasecmp($vars['postfetch'],'delete')) {
437            $this->mail_delete = 1;
438            $this->mail_archivefolder = null;
439        }
440        elseif($vars['postfetch'] && !strcasecmp($vars['postfetch'],'archive') && $vars['mail_archivefolder']) {
441            $this->mail_delete = 0;
442            $this->mail_archivefolder = $vars['mail_archivefolder'];
443        }
444        else {
445            $this->mail_delete = 0;
446            $this->mail_archivefolder = null;
447        }
448
449        if ($vars['passwd']) //New password - encrypt.
450            $this->userpass = Crypto::encrypt($vars['passwd'],SECRET_SALT, $vars['userid']);
451
452        if ($vars['smtp_passwd']) // New SMTP password - encrypt.
453            $this->smtp_userpass = Crypto::encrypt($vars['smtp_passwd'], SECRET_SALT, $vars['smtp_userid']);
454
455        if ($this->save())
456            return true;
457
458        if ($id) { //update
459            $errors['err']=sprintf(__('Unable to update %s.'), __('this email'))
460               .' '.__('Internal error occurred');
461        }
462        else {
463            $errors['err']=sprintf(__('Unable to add %s.'), __('this email'))
464               .' '.__('Internal error occurred');
465        }
466
467        return false;
468    }
469
470    static function validateCredentials($username=null, $password=null, $id=null, &$errors, $smtp=false) {
471        if (!$username)
472            $errors[$smtp ? 'smtp_userid' : 'userid'] = __('Username missing');
473
474        if (!$id && !$password)
475            $errors[$smtp ? 'smtp_passwd' : 'passwd'] = __('Password Required');
476        elseif ($password && $username
477                && !Crypto::encrypt($password, SECRET_SALT, $username))
478            $errors[$smtp ? 'smtp_passwd' : 'passwd'] = sprintf('%s - %s', __('Unable to encrypt password'), __('Get technical help!'));
479
480        return $errors;
481    }
482
483    static function getPermissions() {
484        return self::$perms;
485    }
486
487    static function getAddresses($options=array(), $flat=true) {
488        $objects = static::objects();
489        if ($options['smtp'])
490            $objects = $objects->filter(array('smtp_active'=>true));
491
492        if ($options['depts'])
493            $objects = $objects->filter(array('dept_id__in'=>$options['depts']));
494
495        if (!$flat)
496            return $objects;
497
498        $addresses = array();
499        foreach ($objects->values_flat('email_id', 'email') as $row) {
500            list($id, $email) = $row;
501            $addresses[$id] = $email;
502        }
503        return $addresses;
504    }
505}
506RolePermission::register(/* @trans */ 'Miscellaneous', Email::getPermissions());
507?>
508