1 # Caolan McNamara caolanm@redhat.com
2 # a simple email mailmerge component
4 # manual installation for hackers, not necessary for users
5 # cp mailmerge.py /usr/lib/libreoffice/program
6 # cd /usr/lib/libreoffice/program
7 # ./unopkg add --shared mailmerge.py
8 # edit ~/.openoffice.org2/user/registry/data/org/openoffice/Office/Writer.xcu
9 # and change EMailSupported to as follows...
10 # <prop oor:name="EMailSupported" oor:type="xs:boolean">
20 #to implement com::sun::star::mail::XMailServiceProvider
22 #to implement com.sun.star.mail.XMailMessage
24 from com
.sun
.star
.mail
import XMailServiceProvider
25 from com
.sun
.star
.mail
import XMailService
26 from com
.sun
.star
.mail
import XSmtpService
27 from com
.sun
.star
.mail
import XConnectionListener
28 from com
.sun
.star
.mail
import XAuthenticator
29 from com
.sun
.star
.mail
import XMailMessage
30 from com
.sun
.star
.mail
.MailServiceType
import SMTP
31 from com
.sun
.star
.mail
.MailServiceType
import POP3
32 from com
.sun
.star
.mail
.MailServiceType
import IMAP
33 from com
.sun
.star
.uno
import XCurrentContext
34 from com
.sun
.star
.lang
import IllegalArgumentException
35 from com
.sun
.star
.lang
import EventObject
36 from com
.sun
.star
.lang
import XServiceInfo
37 from com
.sun
.star
.mail
import SendMailMessageFailedException
39 from email
.mime
.base
import MIMEBase
40 from email
.message
import Message
41 from email
.charset
import Charset
42 from email
.charset
import QP
43 from email
.encoders
import encode_base64
44 from email
.header
import Header
45 from email
.mime
.multipart
import MIMEMultipart
46 from email
.utils
import formatdate
47 from email
.utils
import parseaddr
48 from socket
import _GLOBAL_DEFAULT_TIMEOUT
50 import sys
, ssl
, smtplib
, imaplib
, poplib
53 # pythonloader looks for a static g_ImplementationHelper variable
54 g_ImplementationHelper
= unohelper
.ImplementationHelper()
55 g_providerImplName
= "org.openoffice.pyuno.MailServiceProvider"
56 g_messageImplName
= "org.openoffice.pyuno.MailMessage"
58 def prepareTLSContext(xComponent
, xContext
, isTLSRequested
):
59 xConfigProvider
= xContext
.ServiceManager
.createInstance("com.sun.star.configuration.ConfigurationProvider")
60 prop
= uno
.createUnoStruct('com.sun.star.beans.PropertyValue')
61 prop
.Name
= "nodepath"
62 prop
.Value
= "/org.openoffice.Office.Security/Net"
63 xSettings
= xConfigProvider
.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
65 isAllowedInsecure
= xSettings
.getByName("AllowInsecureProtocols")
69 print("SSL config: " + str(ssl
.get_default_verify_paths()), file=sys
.stderr
)
70 tlscontext
= ssl
.create_default_context()
71 # SSLv2/v3 is already disabled by default.
72 # This check does not work, because OpenSSL 3 defines SSL_OP_NO_SSLv2
73 # as 0, so even though _ssl__SSLContext_impl() tries to set it,
74 # getting the value from SSL_CTX_get_options() doesn't lead to setting
75 # the python-level flag.
76 #assert (tlscontext.options & ssl.Options.OP_NO_SSLv2) != 0
77 assert (tlscontext
.options
& ssl
.Options
.OP_NO_SSLv3
) != 0
78 if not(isAllowedInsecure
):
79 if not(isTLSRequested
):
81 print("mailmerge.py: insecure connection not allowed by configuration", file=sys
.stderr
)
82 raise IllegalArgumentException("insecure connection not allowed by configuration", xComponent
, 1)
83 tlscontext
.options |
= ssl
.Options
.OP_NO_TLSv1 | ssl
.Options
.OP_NO_TLSv1_1
86 class PyMailSMTPService(unohelper
.Base
, XSmtpService
):
87 def __init__( self
, ctx
):
90 self
.supportedtypes
= ('Insecure', 'Ssl')
92 self
.connectioncontext
= None
93 self
.notify
= EventObject(self
)
95 print("PyMailSMTPService init", file=sys
.stderr
)
96 print("python version is: " + sys
.version
, file=sys
.stderr
)
97 def addConnectionListener(self
, xListener
):
99 print("PyMailSMTPService addConnectionListener", file=sys
.stderr
)
100 self
.listeners
.append(xListener
)
101 def removeConnectionListener(self
, xListener
):
103 print("PyMailSMTPService removeConnectionListener", file=sys
.stderr
)
104 self
.listeners
.remove(xListener
)
105 def getSupportedConnectionTypes(self
):
107 print("PyMailSMTPService getSupportedConnectionTypes", file=sys
.stderr
)
108 return self
.supportedtypes
109 def connect(self
, xConnectionContext
, xAuthenticator
):
110 self
.connectioncontext
= xConnectionContext
112 print("PyMailSMTPService connect", file=sys
.stderr
)
113 server
= xConnectionContext
.getValueByName("ServerName").strip()
115 print("ServerName: " + server
, file=sys
.stderr
)
116 port
= int(xConnectionContext
.getValueByName("Port"))
118 print("Port: " + str(port
), file=sys
.stderr
)
119 tout
= xConnectionContext
.getValueByName("Timeout")
121 print(isinstance(tout
,int), file=sys
.stderr
)
122 if not isinstance(tout
,int):
123 tout
= _GLOBAL_DEFAULT_TIMEOUT
125 print("Timeout: " + str(tout
), file=sys
.stderr
)
126 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
128 print("ConnectionType: " + connectiontype
, file=sys
.stderr
)
129 tlscontext
= prepareTLSContext(self
, self
.ctx
, connectiontype
.upper() == 'SSL' or port
== 465)
131 self
.server
= smtplib
.SMTP_SSL(server
, port
, timeout
=tout
, context
=tlscontext
)
133 self
.server
= smtplib
.SMTP(server
, port
,timeout
=tout
)
136 self
.server
.set_debuglevel(1)
138 if connectiontype
.upper() == 'SSL' and port
!= 465:
139 # STRIPTLS: smtplib raises an exception if result is not 220
140 self
.server
.starttls(context
=tlscontext
)
142 user
= xAuthenticator
.getUserName()
143 password
= xAuthenticator
.getPassword()
146 print("Logging in, username of: " + user
, file=sys
.stderr
)
147 self
.server
.login(user
, password
)
149 for listener
in self
.listeners
:
150 listener
.connected(self
.notify
)
151 def disconnect(self
):
153 print("PyMailSMTPService disconnect", file=sys
.stderr
)
157 for listener
in self
.listeners
:
158 listener
.disconnected(self
.notify
)
159 def isConnected(self
):
161 print("PyMailSMTPService isConnected", file=sys
.stderr
)
162 return self
.server
!= None
163 def getCurrentConnectionContext(self
):
165 print("PyMailSMTPService getCurrentConnectionContext", file=sys
.stderr
)
166 return self
.connectioncontext
167 def sendMailMessage(self
, xMailMessage
):
171 print("PyMailSMTPService sendMailMessage", file=sys
.stderr
)
172 recipients
= xMailMessage
.getRecipients()
173 sendermail
= xMailMessage
.SenderAddress
174 sendername
= xMailMessage
.SenderName
175 subject
= xMailMessage
.Subject
176 ccrecipients
= xMailMessage
.getCcRecipients()
177 bccrecipients
= xMailMessage
.getBccRecipients()
179 print("PyMailSMTPService subject: " + subject
, file=sys
.stderr
)
180 print("PyMailSMTPService from: " + sendername
, file=sys
.stderr
)
181 print("PyMailSMTPService from: " + sendermail
, file=sys
.stderr
)
182 print("PyMailSMTPService send to: %s" % (recipients
,), file=sys
.stderr
)
184 attachments
= xMailMessage
.getAttachments()
188 content
= xMailMessage
.Body
189 flavors
= content
.getTransferDataFlavors()
191 print("PyMailSMTPService flavors len: %d" % (len(flavors
),), file=sys
.stderr
)
193 #Use first flavor that's sane for an email body
194 for flavor
in flavors
:
195 if flavor
.MimeType
.find('text/html') != -1 or flavor
.MimeType
.find('text/plain') != -1:
197 print("PyMailSMTPService mimetype is: " + flavor
.MimeType
, file=sys
.stderr
)
198 textbody
= content
.getTransferData(flavor
)
201 mimeEncoding
= re
.sub("charset=.*", "charset=UTF-8", flavor
.MimeType
)
202 if mimeEncoding
.find('charset=UTF-8') == -1:
203 mimeEncoding
= mimeEncoding
+ "; charset=UTF-8"
204 textmsg
['Content-Type'] = mimeEncoding
205 textmsg
['MIME-Version'] = '1.0'
208 #it's a string, get it as utf-8 bytes
209 textbody
= textbody
.encode('utf-8')
211 #it's a bytesequence, get raw bytes
212 textbody
= textbody
.value
213 textbody
= textbody
.decode('utf-8')
216 textmsg
.set_payload(textbody
, c
)
220 if (len(attachments
)):
221 msg
= MIMEMultipart()
227 hdr
= Header(sendername
, 'utf-8')
228 hdr
.append('<'+sendermail
+'>','us-ascii')
229 msg
['Subject'] = subject
231 msg
['To'] = COMMASPACE
.join(recipients
)
232 if len(ccrecipients
):
233 msg
['Cc'] = COMMASPACE
.join(ccrecipients
)
234 if xMailMessage
.ReplyToAddress
!= '':
235 msg
['Reply-To'] = xMailMessage
.ReplyToAddress
237 mailerstring
= "LibreOffice via Caolan's mailmerge component"
239 ctx
= uno
.getComponentContext()
240 aConfigProvider
= ctx
.ServiceManager
.createInstance("com.sun.star.configuration.ConfigurationProvider")
241 prop
= uno
.createUnoStruct('com.sun.star.beans.PropertyValue')
242 prop
.Name
= "nodepath"
243 prop
.Value
= "/org.openoffice.Setup/Product"
244 aSettings
= aConfigProvider
.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess",
246 mailerstring
= aSettings
.getByName("ooName") + " " + \
247 aSettings
.getByName("ooSetupVersion") + " via Caolan's mailmerge component"
251 msg
['X-Mailer'] = mailerstring
252 msg
['Date'] = formatdate(localtime
=True)
254 for attachment
in attachments
:
255 content
= attachment
.Data
256 flavors
= content
.getTransferDataFlavors()
258 ctype
= flavor
.MimeType
259 maintype
, subtype
= ctype
.split('/', 1)
260 msgattachment
= MIMEBase(maintype
, subtype
)
261 data
= content
.getTransferData(flavor
)
262 msgattachment
.set_payload(data
.value
)
263 encode_base64(msgattachment
)
264 fname
= attachment
.ReadableName
266 msgattachment
.add_header('Content-Disposition', 'attachment', \
269 msgattachment
.add_header('Content-Disposition', 'attachment', \
270 filename
=('utf-8','',fname
))
272 print(("PyMailSMTPService attachmentheader: ", str(msgattachment
)), file=sys
.stderr
)
274 msg
.attach(msgattachment
)
277 for key
in recipients
:
279 if len(ccrecipients
):
280 for key
in ccrecipients
:
282 if len(bccrecipients
):
283 for key
in bccrecipients
:
285 truerecipients
= uniquer
.keys()
288 print(("PyMailSMTPService recipients are: ", truerecipients
), file=sys
.stderr
)
290 self
.server
.sendmail(sendermail
, truerecipients
, msg
.as_string())
292 class PyMailIMAPService(unohelper
.Base
, XMailService
):
293 def __init__( self
, ctx
):
296 self
.supportedtypes
= ('Insecure', 'Ssl')
298 self
.connectioncontext
= None
299 self
.notify
= EventObject(self
)
301 print("PyMailIMAPService init", file=sys
.stderr
)
302 def addConnectionListener(self
, xListener
):
304 print("PyMailIMAPService addConnectionListener", file=sys
.stderr
)
305 self
.listeners
.append(xListener
)
306 def removeConnectionListener(self
, xListener
):
308 print("PyMailIMAPService removeConnectionListener", file=sys
.stderr
)
309 self
.listeners
.remove(xListener
)
310 def getSupportedConnectionTypes(self
):
312 print("PyMailIMAPService getSupportedConnectionTypes", file=sys
.stderr
)
313 return self
.supportedtypes
314 def connect(self
, xConnectionContext
, xAuthenticator
):
316 print("PyMailIMAPService connect", file=sys
.stderr
)
318 self
.connectioncontext
= xConnectionContext
319 server
= xConnectionContext
.getValueByName("ServerName")
321 print(server
, file=sys
.stderr
)
322 port
= int(xConnectionContext
.getValueByName("Port"))
324 print(port
, file=sys
.stderr
)
325 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
327 print(connectiontype
, file=sys
.stderr
)
328 tlscontext
= prepareTLSContext(self
, self
.ctx
, connectiontype
.upper() == 'SSL')
329 print("BEFORE", file=sys
.stderr
)
330 if connectiontype
.upper() == 'SSL':
331 self
.server
= imaplib
.IMAP4_SSL(server
, port
, ssl_context
=tlscontext
)
333 self
.server
= imaplib
.IMAP4(server
, port
)
334 print("AFTER", file=sys
.stderr
)
336 user
= xAuthenticator
.getUserName()
337 password
= xAuthenticator
.getPassword()
340 print("Logging in, username of: " + user
, file=sys
.stderr
)
341 self
.server
.login(user
, password
)
343 for listener
in self
.listeners
:
344 listener
.connected(self
.notify
)
345 def disconnect(self
):
347 print("PyMailIMAPService disconnect", file=sys
.stderr
)
351 for listener
in self
.listeners
:
352 listener
.disconnected(self
.notify
)
353 def isConnected(self
):
355 print("PyMailIMAPService isConnected", file=sys
.stderr
)
356 return self
.server
!= None
357 def getCurrentConnectionContext(self
):
359 print("PyMailIMAPService getCurrentConnectionContext", file=sys
.stderr
)
360 return self
.connectioncontext
362 class PyMailPOP3Service(unohelper
.Base
, XMailService
):
363 def __init__( self
, ctx
):
366 self
.supportedtypes
= ('Insecure', 'Ssl')
368 self
.connectioncontext
= None
369 self
.notify
= EventObject(self
)
371 print("PyMailPOP3Service init", file=sys
.stderr
)
372 def addConnectionListener(self
, xListener
):
374 print("PyMailPOP3Service addConnectionListener", file=sys
.stderr
)
375 self
.listeners
.append(xListener
)
376 def removeConnectionListener(self
, xListener
):
378 print("PyMailPOP3Service removeConnectionListener", file=sys
.stderr
)
379 self
.listeners
.remove(xListener
)
380 def getSupportedConnectionTypes(self
):
382 print("PyMailPOP3Service getSupportedConnectionTypes", file=sys
.stderr
)
383 return self
.supportedtypes
384 def connect(self
, xConnectionContext
, xAuthenticator
):
386 print("PyMailPOP3Service connect", file=sys
.stderr
)
388 self
.connectioncontext
= xConnectionContext
389 server
= xConnectionContext
.getValueByName("ServerName")
391 print(server
, file=sys
.stderr
)
392 port
= int(xConnectionContext
.getValueByName("Port"))
394 print(port
, file=sys
.stderr
)
395 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
397 print(connectiontype
, file=sys
.stderr
)
398 tlscontext
= prepareTLSContext(self
, self
.ctx
, connectiontype
.upper() == 'SSL')
399 print("BEFORE", file=sys
.stderr
)
400 if connectiontype
.upper() == 'SSL':
401 self
.server
= poplib
.POP3_SSL(server
, port
, context
=tlscontext
)
403 tout
= xConnectionContext
.getValueByName("Timeout")
405 print(isinstance(tout
,int), file=sys
.stderr
)
406 if not isinstance(tout
,int):
407 tout
= _GLOBAL_DEFAULT_TIMEOUT
409 print("Timeout: " + str(tout
), file=sys
.stderr
)
410 self
.server
= poplib
.POP3(server
, port
, timeout
=tout
)
411 print("AFTER", file=sys
.stderr
)
413 user
= xAuthenticator
.getUserName()
414 password
= xAuthenticator
.getPassword()
416 print("Logging in, username of: " + user
, file=sys
.stderr
)
417 self
.server
.user(user
)
418 self
.server
.pass_(password
)
420 for listener
in self
.listeners
:
421 listener
.connected(self
.notify
)
422 def disconnect(self
):
424 print("PyMailPOP3Service disconnect", file=sys
.stderr
)
428 for listener
in self
.listeners
:
429 listener
.disconnected(self
.notify
)
430 def isConnected(self
):
432 print("PyMailPOP3Service isConnected", file=sys
.stderr
)
433 return self
.server
!= None
434 def getCurrentConnectionContext(self
):
436 print("PyMailPOP3Service getCurrentConnectionContext", file=sys
.stderr
)
437 return self
.connectioncontext
439 class PyMailServiceProvider(unohelper
.Base
, XMailServiceProvider
, XServiceInfo
):
440 def __init__( self
, ctx
):
442 print("PyMailServiceProvider init", file=sys
.stderr
)
444 def create(self
, aType
):
446 print("PyMailServiceProvider create with", aType
, file=sys
.stderr
)
448 return PyMailSMTPService(self
.ctx
);
450 return PyMailPOP3Service(self
.ctx
);
452 return PyMailIMAPService(self
.ctx
);
454 print("PyMailServiceProvider, unknown TYPE " + aType
, file=sys
.stderr
)
456 def getImplementationName(self
):
457 return g_providerImplName
459 def supportsService(self
, ServiceName
):
460 return g_ImplementationHelper
.supportsService(g_providerImplName
, ServiceName
)
462 def getSupportedServiceNames(self
):
463 return g_ImplementationHelper
.getSupportedServiceNames(g_providerImplName
)
465 class PyMailMessage(unohelper
.Base
, XMailMessage
):
466 def __init__( self
, ctx
, sTo
='', sFrom
='', Subject
='', Body
=None, aMailAttachment
=None ):
468 print("PyMailMessage init", file=sys
.stderr
)
471 self
.recipients
= [sTo
]
472 self
.ccrecipients
= []
473 self
.bccrecipients
= []
474 self
.aMailAttachments
= []
475 if aMailAttachment
!= None:
476 self
.aMailAttachments
.append(aMailAttachment
)
478 self
.SenderName
, self
.SenderAddress
= parseaddr(sFrom
)
479 self
.ReplyToAddress
= sFrom
480 self
.Subject
= Subject
483 print("post PyMailMessage init", file=sys
.stderr
)
484 def addRecipient( self
, recipient
):
486 print("PyMailMessage.addRecipient: " + recipient
, file=sys
.stderr
)
487 self
.recipients
.append(recipient
)
488 def addCcRecipient( self
, ccrecipient
):
490 print("PyMailMessage.addCcRecipient: " + ccrecipient
, file=sys
.stderr
)
491 self
.ccrecipients
.append(ccrecipient
)
492 def addBccRecipient( self
, bccrecipient
):
494 print("PyMailMessage.addBccRecipient: " + bccrecipient
, file=sys
.stderr
)
495 self
.bccrecipients
.append(bccrecipient
)
496 def getRecipients( self
):
498 print("PyMailMessage.getRecipients: " + str(self
.recipients
), file=sys
.stderr
)
499 return tuple(self
.recipients
)
500 def getCcRecipients( self
):
502 print("PyMailMessage.getCcRecipients: " + str(self
.ccrecipients
), file=sys
.stderr
)
503 return tuple(self
.ccrecipients
)
504 def getBccRecipients( self
):
506 print("PyMailMessage.getBccRecipients: " + str(self
.bccrecipients
), file=sys
.stderr
)
507 return tuple(self
.bccrecipients
)
508 def addAttachment( self
, aMailAttachment
):
510 print("PyMailMessage.addAttachment", file=sys
.stderr
)
511 self
.aMailAttachments
.append(aMailAttachment
)
512 def getAttachments( self
):
514 print("PyMailMessage.getAttachments", file=sys
.stderr
)
515 return tuple(self
.aMailAttachments
)
517 def getImplementationName(self
):
518 return g_messageImplName
520 def supportsService(self
, ServiceName
):
521 return g_ImplementationHelper
.supportsService(g_messageImplName
, ServiceName
)
523 def getSupportedServiceNames(self
):
524 return g_ImplementationHelper
.getSupportedServiceNames(g_messageImplName
)
526 g_ImplementationHelper
.addImplementation( \
527 PyMailServiceProvider
, g_providerImplName
,
528 ("com.sun.star.mail.MailServiceProvider",),)
529 g_ImplementationHelper
.addImplementation( \
530 PyMailMessage
, g_messageImplName
,
531 ("com.sun.star.mail.MailMessage",),)
533 # vim: set shiftwidth=4 softtabstop=4 expandtab: