############################################################################## # # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # ############################################################################## import os import sys import bz2 import time from datetime import datetime from datetime import timedelta import release import socket import base64 import binascii import random import smtplib import mimetypes from email import Encoders from optparse import OptionParser from email.Message import Message from email.MIMEBase import MIMEBase from email.MIMEText import MIMEText from email.MIMEMultipart import MIMEMultipart from email.Utils import COMMASPACE, formatdate import netsvc import pooler import tools from osv import fields from osv import osv from tools.translate import _ error_msg = { 'not_active' : "Please activate Email Server, without activating you can not send Email(s).", 'server_stop' : 'Please start Email Server, without starting you can not send Email(s).', 'server_not_confirm' : 'Please Verify Email Server, without verifying you can not send Email(s).' } logger = netsvc.Logger() class smtpclient(osv.osv): _name = 'email.smtpclient' _description = 'Email Client' _columns = { 'name' : fields.char('Server Name', size=256, required=True), 'from_email' : fields.char('Email From', size=256), 'email':fields.char('Email Address', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}), 'cc_to':fields.char('Send copy to', size=256, readonly=True, states={'new':[('readonly',False)]}, help="use comma to supply multiple address. email@domain.com, email2@domain.com"), 'bcc_to':fields.char('Send blind copy to', size=256, readonly=True, states={'new':[('readonly',False)]}, help="use comma to supply multiple address. email@domain.com, email2@domain.com"), 'user' : fields.char('User Name', size=256, readonly=True, states={'new':[('readonly',False)]}), 'password' : fields.char('Password', size=1024, invisible=True, readonly=True, states={'new':[('readonly',False)]}), 'server' : fields.char('SMTP Server', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}), 'auth' : fields.boolean("Use Auth", readonly=True, states={'new':[('readonly',False)]}), 'port' : fields.char('SMTP Port', size=256, required=True, readonly=True, states={'new':[('readonly',False)]}), 'ssl' : fields.boolean("Use SSL?", readonly=True, states={'new':[('readonly',False)]}), 'users_id': fields.many2many('res.users', 'res_smtpserver_group_rel', 'sid', 'uid', 'Users Allowed'), 'state': fields.selection([ ('new','Not Verified'), ('waiting','Waiting for Verification'), ('confirm','Verified'), ],'Server Status', select=True, readonly=True), 'auth_type':fields.selection([('gmail','Google Server'), ('yahoo','Yahoo!!! Server'), ('unknown','Other Mail Servers')], string="Server Type", readonly=True, states={'new':[('readonly',False)]}), 'active' : fields.boolean("Active"), 'date_create': fields.date('Date Create', required=True, readonly=True), 'test_email' : fields.text('Test Message', translate=True), 'body' : fields.text('Message', translate=True, help="The message text that will be send along with the email which is send through this server"), 'verify_email' : fields.text('Verify Message', translate=True, readonly=True, states={'new':[('readonly',False)]}), 'code' : fields.char('Verification Code', size=1024), 'type' : fields.selection([("default", "Default"),("account", "Account"),("sale","Sale"),("stock","Stock")], "Server Type",required=True), 'history_line': fields.one2many('email.smtpclient.history', 'server_id', 'History'), 'server_statistics': fields.one2many('report.smtp.server', 'server_id', 'Statistics'), 'delete_queue': fields.selection([ ('never','Never Delete Message'), ('content','Delete Content After'), ('all','Clear All After'), ('after_send','Delete when Email Sent'), ],'Queue Option', select=True), 'priority': fields.integer('Server Priority', readonly=True, states={'new':[('readonly',False)]}, help="Priority between 0 to 10, will be used to define the MTA process priotiry"), 'header_ids':fields.one2many('email.headers', 'server_id', 'Default Headers'), 'disclaimers': fields.text('Disclaimers'), 'process_id': fields.many2one('ir.cron', 'MTA Process', readonly=True, help="Mail Transport Agent Process"), 'pstate': fields.selection([ ('running','Running'), ('stop','Stop'), ],'Server Statue', select=True, readonly=True), 'delete_queue_period': fields.integer('Delete after', help="delete emails/contents from email queue after specified no of days"), } def _get_users(self, cr, uid, context={}): return self.pool.get('res.users').search(cr, uid, []) _defaults = { 'date_create': lambda *a: time.strftime('%Y-%m-%d'), 'state': lambda *a: 'new', 'type': lambda *a: 'default', 'port': lambda *a: '25', 'pstate':lambda *a: 'stop', 'priority': lambda *a: 5, 'delete_queue_period': lambda *a: 30, 'auth': lambda *a: True, 'active': lambda *a: True, 'delete_queue': lambda *a: 'never', 'users_id': _get_users, 'verify_email': lambda *a: _("Verification Message. This is the code\n\n__code__\n\nyou must copy in the OpenERP Email Server (Verify Server wizard).\n\nCreated by user __user__"), } server = {} smtpServer = {} def create(self, cr, user, vals, context={}): if vals.get('password', False) != False: vals['password'] = base64.b64encode(vals.get('password')) res_id = super(smtpclient, self).create(cr, user, vals, context) return res_id def write(self, cr, user, ids, vals, context=None): flag = False if vals.get('password', False) != False: for pass_char in vals.get('password'): if pass_char != '*': flag= True break if flag: vals['password'] = base64.b64encode(vals.get('password')) else: del vals['password'] res = super(smtpclient, self).write(cr, user, ids, vals, context) return res def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'): def override_password(o): if len(o) > 0: for field in o[0]: if field == 'password': o[0][field] = '********' return o result = super(smtpclient, self).read(cr, uid, ids, fields, context, load) result = override_password(result) return result def change_servertype(self, cr, uid, ids, server): if server == 'gmail': return {'value':{'server':'smtp.gmail.com', 'port':'25', 'ssl':True, 'auth':True}} elif server== 'yahoo': return {'value':{'server':'smtp.mail.yahoo.co.in', 'ssl':False, 'port':'587', 'auth':True}} else: return {'value':{'server':'localhost', 'port':'25', 'ssl':False, 'auth':False}} def change_email(self, cr, uid, ids, email): email_from = self.pool.get('res.users').browse(cr, uid, uid).name if len(email) > 0 and email.find('@') > -1 and email.index('@') > 0: user = email[0:email.index('@')] return {'value':{'user':user, 'from_email':email_from+' <'+email+'>'}} else: return {'value':{'user':email, 'from_email':email_from+' <'+email+'>'}} def check_permissions(self, cr, uid, ids): if uid == 1: return True cr.execute('select * from res_smtpserver_group_rel where sid=%s and uid=%s' % (ids[0], uid)) data = cr.fetchall() if len(data) <= 0: return False return True def gen_private_key(self, cr, uid, ids): new_key = [] for i in time.strftime('%Y-%m-%d %H:%M:%S'): ky = i if ky in (' ', '-', ':'): keys = random.random() key = str(keys).split('.')[1] ky = key new_key.append(ky) new_key.sort() key = ''.join(new_key) return key def _set_error(self, cr, uid, server_id, context={}): server_obj = self.browse(cr, uid, server_id) if not server_obj.active: return 'not_active' if server_obj.pstate == 'stop' : return 'server_stop' if server_obj.state != 'confirm': return 'server_not_confirm' return True def test_verify_email(self, cr, uid, ids, toemail, test=False, code=False): serverid = ids[0] self.open_connection(cr, uid, ids, serverid) key = False if test and self.server[serverid]['state'] == 'confirm': body = self.server[serverid]['test_email'] or '' else: body = self.server[serverid]['verify_email'] or '' #ignore the code key = self.gen_private_key(cr, uid, ids) #md5(time.strftime('%Y-%m-%d %H:%M:%S') + toemail).hexdigest(); body = body.replace("__code__", key) user = pooler.get_pool(cr.dbname).get('res.users').browse(cr, uid, [uid])[0] body = body.replace("__user__", user.name) if len(body.strip()) <= 0: raise osv.except_osv(_('Message Error!'), _('Please configure Email Server Messages [Verification / Test]')) try: msg = MIMEText(body.encode('utf8') or '',_subtype='plain',_charset='utf-8') except: msg = MIMEText(body or '',_subtype='plain',_charset='utf-8') if not test and not self.server[serverid]['state'] == 'confirm': msg['Subject'] = _('OpenERP SMTP server Email Registration Code!') else: msg['Subject'] = _('OpenERP Test Email!') msg['To'] = toemail msg['From'] = tools.ustr(self.server[serverid]['from_email']) message = msg.as_string() if self.server[serverid]['disclaimers']: body = body + "\n" + self.server[serverid]['disclaimers'] queue = pooler.get_pool(cr.dbname).get('email.smtpclient.queue') queue.create(cr, uid, { 'to':toemail, 'server_id':serverid, 'name':msg['Subject'], 'body':body, 'serialized_message':message, 'priority':1, 'type':'system' }) if self.server[serverid]['state'] != 'confirm': self.write(cr, uid, ids, {'state':'waiting', 'code':key}) return True def getpassword(self, cr, uid, ids): data = {} cr.execute("select * from email_smtpclient where id = %s" , (str(ids[0]),)) data = cr.dictfetchall() return data def open_connection(self, cr, uid, ids, serverid=False, permission=True): if serverid: self.server[serverid] = self.getpassword(cr, uid, [serverid])[0] else: raise osv.except_osv(_('Read Error!'), _('Unable to read Server Settings')) if permission: if not self.check_permissions(cr, uid, [serverid]): raise osv.except_osv(_('Permission Error!'), _('You have no permission to access SMTP Server : %s ') % (self.server[serverid]['name'],) ) if self.server[serverid]: try: self.smtpServer[serverid] = smtplib.SMTP() self.smtpServer[serverid].debuglevel = 0 self.smtpServer[serverid].connect(str(self.server[serverid]['server']),str(self.server[serverid]['port'])) if self.server[serverid]['ssl']: self.smtpServer[serverid].ehlo() self.smtpServer[serverid].starttls() self.smtpServer[serverid].ehlo() if self.server[serverid]['auth']: password = self.server[serverid]['password'] try: password = base64.b64decode(password) except: pass self.smtpServer[serverid].login(str(self.server[serverid]['user']), password) except Exception, e: logger.notifyChannel('imap', netsvc.LOG_WARNING, e) return True def selectAddress(self, cr, uid, partner=None, contact=None, ): email = 'none@none.com' if partner is None and contact is None: return 'none@none.com' if partner is not None and contact is None: pool = self.pool.get('res.partner') data = pool.read(cr, uid, [partner])[0] if data: contact = data['address'] if contact is not None: pool = self.pool.get('res.partner.address') data = pool.read(cr, uid, contact)[0] email = data['email'] return email def select(self, cr, uid, type): pool = self.pool.get('email.smtpclient') ids = pool.search(cr, uid, [('type','=',type)], context=False) if not ids: ids = pool.search(cr, uid, [('type','=','default')], context=False) if not ids: return False return ids[0] # Reports is a list of tuples,where first arguement of tuple is the name of the report,second is the list of ids of the object def send_email(self, cr, uid, server_id, emailto, subject, body='', attachments=[], reports=[], ir_attach=[], charset='utf-8', headers={}, context={}): if not emailto: raise osv.except_osv(_('SMTP Data Error !'), _('Email TO Address not Defined !')) def createReport(cr, uid, report, ids, name=False): files = [] for id in ids: try: service = netsvc.LocalService(report) (result, format) = service.create(cr, uid, [id], {}, {}) if not name: report_file = '/tmp/reports'+ str(id) + '.pdf' else: report_file = name fp = open(report_file,'wb+') fp.write(result); fp.close(); files += [report_file] except Exception,e: continue return files smtp_server = self.browse(cr, uid, server_id) if smtp_server.state != 'confirm': raise osv.except_osv(_('SMTP Server Error !'), _('Server is not Verified, Please Verify the Server !')) if not subject: subject = "OpenERP Email: [Unknown Subject]" try: subject = subject.encode(charset) except: subject = subject.decode() #attachment from Reports for rpt in reports: if len(rpt) == 3: rpt_file = createReport(cr, uid, rpt[0], rpt[1], rpt[2]) elif len(rpt) == 2: rpt_file = createReport(cr, uid, rpt[0], rpt[1]) attachments += rpt_file if isinstance(emailto, str) or isinstance(emailto, unicode): emailto = [emailto] ir_pool = self.pool.get('ir.attachment') for to in emailto: msg = MIMEMultipart() msg['Subject'] = tools.ustr(subject) msg['To'] = to msg['From'] = context.get('email_from', smtp_server.from_email) if body == False: body = '' if smtp_server.disclaimers: body = body + "\n" + smtp_server.disclaimers try: msg.attach(MIMEText(body.encode(charset) or '', _charset=charset, _subtype="html")) except: msg.attach(MIMEText(body or '', _charset=charset, _subtype="html")) #add custom headers to email for hk in headers.keys(): msg[hk] = headers[hk] for hk in smtp_server.header_ids: msg[hk.key] = hk.value context_headers = context.get('headers', []) for hk in context_headers: msg[hk] = context_headers[hk] # Add OpenERP Server information msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)' msg['X-OpenERP-Server-Host'] = socket.gethostname() msg['X-OpenERP-Server-Version'] = release.version msg['Message-Id'] = "<%s-openerp-@%s>" % (time.time(), socket.gethostname()) if smtp_server.cc_to: msg['Cc'] = smtp_server.cc_to if smtp_server.bcc_to: msg['Bcc'] = smtp_server.bcc_to #attach files from disc for file in attachments: part = MIMEBase('application', "octet-stream") part.set_payload(open(file,"rb").read()) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file)) msg.attach(part) #attach files from ir_attachments for ath in ir_pool.browse(cr, uid, ir_attach): part = MIMEBase('application', "octet-stream") datas = base64.decodestring(ath.datas) part.set_payload(datas) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' %(ath.name)) msg.attach(part) message = msg.as_string() data = { 'to':to, 'server_id':server_id, 'cc':smtp_server.cc_to or False, 'bcc':smtp_server.bcc_to or False, 'name':subject, 'body':body, 'serialized_message':message, 'priority':smtp_server.priority, } self.create_queue_enrty(cr, uid, data, context) return True def create_queue_enrty(self, cr, uid, data, context={}): queue = pooler.get_pool(cr.dbname).get('email.smtpclient.queue') return queue.create(cr, uid, data, context) def _check_history(self, cr, uid, ids=False, context={}): result = True server = self.pool.get('email.smtpclient') queue = self.pool.get('email.smtpclient.queue') sids = self.search(cr, uid, []) for server in self.browse(cr, uid, sids): if server.delete_queue == 'never': continue now = datetime.today() days = timedelta(days=server.delete_queue_period) day = now - days kday = day.__str__().split(' ')[0] if server.delete_queue == 'content': qids = queue.search(cr, uid, [('server_id','=',server.id), ('date_create','<=',kday)]) queue.write(cr, uid, qids, {'serialized_message':False}) continue if server.delete_queue == 'all': qids = queue.search(cr, uid, [('server_id','=',server.id), ('date_create','<=',kday)]) queue.unlink(cr, uid, qids) return result def _send_emails(self, cr, uid, ids, context={}): queue = self.pool.get('email.smtpclient.queue') history = self.pool.get('email.smtpclient.history') queue.write(cr, uid, ids, {'state':'sending'}) error = [] sent = [] remove = [] open_server = [] for email in queue.browse(cr, uid, ids): if not email.server_id.id in open_server: open_server.append(email.server_id.id) self.open_connection(cr, uid, ids, email.server_id.id) try: self.smtpServer[email.server_id.id].sendmail(email.server_id.email, [email.to, email.cc, email.bcc], tools.ustr(email.serialized_message)) message = "message sent successfully to %s from %s server" % (email.to, email.server_id.name) logger.notifyChannel('smtp', netsvc.LOG_INFO, message) except Exception, e: queue.write(cr, uid, [email.id], {'error':e, 'state':'error'}) continue history.create(cr, uid, { 'name':email.body, 'user_id':uid, 'server_id': email.server_id.id, 'email':email.to }) if email.server_id.delete_queue == 'after_send': remove.append(email.id) else: sent.append(email.id) queue.unlink(cr, uid, remove) queue.write(cr, uid, sent, {'state':'send'}) return True def _check_queue(self, cr, uid, ids=False): queue = self.pool.get('email.smtpclient.queue') sids = [] if not ids: sids = queue.search(cr, uid, [('state','not in',['send','sending']), ('type','=','system')], order="priority", limit=30) ids =[] else: sids = queue.search(cr, uid, [('state','not in',['send','sending']), ('server_id','in',ids)], order="priority", limit=30) message = "" if len(ids) > 1: message = "sending %s emails from message queuq !" % (len(ids)) logger.notifyChannel('smtp', netsvc.LOG_INFO, message) result = self. _send_emails(cr, uid, sids, {}) return result def set_to_draft(self, cr, uid, ids, context={}): self.stop_process(cr, uid, ids, context) self.write(cr, uid, ids, {'state':'new', 'code':False}) return True def create_process(self, cr, uid, ids, context={}): svr = self.browse(cr, uid, ids[0]) if not svr.process_id: res = { 'name':'Process : ' + svr.name, 'model':'email.smtpclient', 'args': repr([ids]), 'function':'_check_queue', 'priority':5, 'interval_number':1, 'interval_type':'minutes', 'user_id':uid, 'numbercall':-1, 'doall':False, 'active':False } id = self.pool.get('ir.cron').create(cr, uid, res) self.write(cr, uid, ids, {'process_id':id}) return True def start_process(self, cr, uid, ids, context={}): process = self.browse(cr, uid, ids[0], context) if not process.process_id or process.state != 'confirm': raise osv.except_osv(_('SMTP Server Error !'), _('Server is not Verified, Please Verify the Server !')) pid = process.process_id.id self.pool.get('ir.cron').write(cr, uid, [pid], {'active':True}) self.write(cr, uid, ids, {'pstate':'running'}) return True def stop_process(self, cr, uid, ids, context={}): pid = self.browse(cr, uid, ids[0], context).process_id.id self.pool.get('ir.cron').write(cr, uid, [pid], {'active':False}) self.write(cr, uid, ids, {'pstate':'stop'}) return True smtpclient() class email_headers(osv.osv): _name = 'email.headers' _description = 'Email Headers' _columns = { 'server_id':fields.many2one('email.smtpclient', 'SMTP Server'), 'key':fields.char('Header', size=64, required=True), 'value':fields.char('Value', size=1024, required=False), } email_headers() class email_history(osv.osv): _name = 'email.smtpclient.history' _description = 'Email Client History' _order = 'id desc' _columns = { 'name' : fields.text('Description',required=True, readonly=True), 'date_create': fields.datetime('Date',readonly=True), 'user_id':fields.many2one('res.users', 'Username', readonly=True, select=True), 'server_id' : fields.many2one('email.smtpclient', 'Smtp Server', ondelete='set null', readonly=True, required=True), 'model':fields.many2one('ir.model', 'Model', readonly=True, select=True), 'resource_id':fields.integer('Resource ID', readonly=True), 'email':fields.char('Email',size=64,readonly=True), } _defaults = { 'date_create': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'user_id': lambda obj, cr, uid, context: uid, } def create(self, cr, uid, vals, context=None): super(email_history,self).create(cr, uid, vals, context) cr.commit() email_history() class message_queue(osv.osv): _name = 'email.smtpclient.queue' _description = 'Email Queue' _order = '"to"' _columns = { 'to' : fields.char('Mail to', size=1024, readonly=True, states={'draft':[('readonly',False)], 'error':[('readonly',False)]}), 'server_id':fields.many2one('email.smtpclient', 'SMTP Server', readonly=True, states={'draft':[('readonly',False)]}), 'cc' : fields.char('CC to', size=1024, readonly=True, states={'draft':[('readonly',False)]}), 'bcc' : fields.char('BCC to', size=1024, readonly=True, states={'draft':[('readonly',False)]}), 'name' : fields.char('Subject', size=1024, readonly=True, states={'draft':[('readonly',False)]}), 'body' : fields.text('Email Text', readonly=True, states={'draft':[('readonly',False)]}), 'serialized_message':fields.text('Message', readonly=True, states={'draft':[('readonly',False)]}), 'state':fields.selection([ ('draft','Queued'), ('sending','Waiting'), ('send','Sent'), ('error','Error'), ],'Message Status', select=True, readonly=True), 'type':fields.selection([ ('default','Default Message'), ('system','System Message'), ],'Message Type', select=True, readonly=True), 'error':fields.text('Last Error', size=256, readonly=True, states={'draft':[('readonly',False)]}), 'date_create': fields.datetime('Date', readonly=True), 'priority':fields.integer('Message Priority', readonly=True), } _defaults = { 'date_create': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'state': lambda *a: 'draft', 'priority': lambda *a: '10', 'type': lambda *a: 'default', } message_queue() class report_smtp_server(osv.osv): _name = "report.smtp.server" _description = "Server Statistics" _auto = False _columns = { 'server_id':fields.many2one('email.smtpclient','Server ID',readonly=True), 'name': fields.char('Server',size=64,readonly=True), 'history':fields.char('History',size=64, readonly=True), 'no':fields.integer('Total No.',readonly=True), } def init(self, cr): cr.execute(""" create or replace view report_smtp_server as ( select min(h.id) as id, c.id as server_id, h.name as history, h.name as name, count(h.name) as no from email_smtpclient c inner join email_smtpclient_history h on c.id=h.server_id group by h.name, c.id ) """) report_smtp_server() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: