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">
18 # to implement com::sun::star::mail::XMailServiceProvider
20 # to implement com.sun.star.mail.XMailMessage
22 from com
.sun
.star
.mail
import XMailServiceProvider
23 from com
.sun
.star
.mail
import XMailService
24 from com
.sun
.star
.mail
import XSmtpService
25 from com
.sun
.star
.mail
import XMailMessage
26 from com
.sun
.star
.mail
.MailServiceType
import SMTP
27 from com
.sun
.star
.mail
.MailServiceType
import POP3
28 from com
.sun
.star
.mail
.MailServiceType
import IMAP
29 from com
.sun
.star
.lang
import IllegalArgumentException
30 from com
.sun
.star
.lang
import EventObject
31 from com
.sun
.star
.lang
import XServiceInfo
33 from email
.mime
.base
import MIMEBase
34 from email
.message
import Message
35 from email
.charset
import Charset
36 from email
.charset
import QP
37 from email
.encoders
import encode_base64
38 from email
.header
import Header
39 from email
.mime
.multipart
import MIMEMultipart
40 from email
.utils
import formatdate
41 from email
.utils
import parseaddr
42 from socket
import _GLOBAL_DEFAULT_TIMEOUT
52 # pythonloader looks for a static g_ImplementationHelper variable
53 g_ImplementationHelper
= unohelper
.ImplementationHelper()
54 g_providerImplName
= "org.openoffice.pyuno.MailServiceProvider"
55 g_messageImplName
= "org.openoffice.pyuno.MailMessage"
58 def prepareTLSContext(xComponent
, xContext
, isTLSRequested
):
59 xConfigProvider
= xContext
.ServiceManager
.createInstance(
60 "com.sun.star.configuration.ConfigurationProvider"
62 prop
= uno
.createUnoStruct("com.sun.star.beans.PropertyValue")
63 prop
.Name
= "nodepath"
64 prop
.Value
= "/org.openoffice.Office.Security/Net"
65 xSettings
= xConfigProvider
.createInstanceWithArguments(
66 "com.sun.star.configuration.ConfigurationAccess", (prop
,)
68 isAllowedInsecure
= xSettings
.getByName("AllowInsecureProtocols")
72 print("SSL config: " + str(ssl
.get_default_verify_paths()), file=sys
.stderr
)
73 tlscontext
= ssl
.create_default_context()
74 # SSLv2/v3 is already disabled by default.
75 # This check does not work, because OpenSSL 3 defines SSL_OP_NO_SSLv2
76 # as 0, so even though _ssl__SSLContext_impl() tries to set it,
77 # getting the value from SSL_CTX_get_options() doesn't lead to setting
78 # the python-level flag.
79 # assert (tlscontext.options & ssl.Options.OP_NO_SSLv2) != 0
80 assert (tlscontext
.options
& ssl
.Options
.OP_NO_SSLv3
) != 0
81 if not (isAllowedInsecure
):
82 if not (isTLSRequested
):
85 "mailmerge.py: insecure connection not allowed by configuration",
88 raise IllegalArgumentException(
89 "insecure connection not allowed by configuration", xComponent
, 1
91 tlscontext
.options |
= ssl
.Options
.OP_NO_TLSv1 | ssl
.Options
.OP_NO_TLSv1_1
95 class PyMailSMTPService(unohelper
.Base
, XSmtpService
):
96 def __init__(self
, ctx
):
99 self
.supportedtypes
= ("Insecure", "Ssl")
101 self
.connectioncontext
= None
102 self
.notify
= EventObject(self
)
104 print("PyMailSMTPService init", file=sys
.stderr
)
105 print("python version is: " + sys
.version
, file=sys
.stderr
)
107 def addConnectionListener(self
, xListener
):
109 print("PyMailSMTPService addConnectionListener", file=sys
.stderr
)
110 self
.listeners
.append(xListener
)
112 def removeConnectionListener(self
, xListener
):
114 print("PyMailSMTPService removeConnectionListener", file=sys
.stderr
)
115 self
.listeners
.remove(xListener
)
117 def getSupportedConnectionTypes(self
):
119 print("PyMailSMTPService getSupportedConnectionTypes", file=sys
.stderr
)
120 return self
.supportedtypes
122 def connect(self
, xConnectionContext
, xAuthenticator
):
123 self
.connectioncontext
= xConnectionContext
125 print("PyMailSMTPService connect", file=sys
.stderr
)
126 server
= xConnectionContext
.getValueByName("ServerName").strip()
128 print("ServerName: " + server
, file=sys
.stderr
)
129 port
= int(xConnectionContext
.getValueByName("Port"))
131 print("Port: " + str(port
), file=sys
.stderr
)
132 tout
= xConnectionContext
.getValueByName("Timeout")
134 print(isinstance(tout
, int), file=sys
.stderr
)
135 if not isinstance(tout
, int):
136 tout
= _GLOBAL_DEFAULT_TIMEOUT
138 print("Timeout: " + str(tout
), file=sys
.stderr
)
139 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
141 print("ConnectionType: " + connectiontype
, file=sys
.stderr
)
142 tlscontext
= prepareTLSContext(
143 self
, self
.ctx
, connectiontype
.upper() == "SSL" or port
== 465
146 self
.server
= smtplib
.SMTP_SSL(
147 server
, port
, timeout
=tout
, context
=tlscontext
150 self
.server
= smtplib
.SMTP(server
, port
, timeout
=tout
)
153 self
.server
.set_debuglevel(1)
155 if connectiontype
.upper() == "SSL" and port
!= 465:
156 # STRIPTLS: smtplib raises an exception if result is not 220
157 self
.server
.starttls(context
=tlscontext
)
159 user
= xAuthenticator
.getUserName()
160 password
= xAuthenticator
.getPassword()
163 print("Logging in, username of: " + user
, file=sys
.stderr
)
164 self
.server
.login(user
, password
)
166 for listener
in self
.listeners
:
167 listener
.connected(self
.notify
)
169 def disconnect(self
):
171 print("PyMailSMTPService disconnect", file=sys
.stderr
)
175 for listener
in self
.listeners
:
176 listener
.disconnected(self
.notify
)
178 def isConnected(self
):
180 print("PyMailSMTPService isConnected", file=sys
.stderr
)
181 return self
.server
is not None
183 def getCurrentConnectionContext(self
):
185 print("PyMailSMTPService getCurrentConnectionContext", file=sys
.stderr
)
186 return self
.connectioncontext
188 def sendMailMessage(self
, xMailMessage
):
192 print("PyMailSMTPService sendMailMessage", file=sys
.stderr
)
193 recipients
= xMailMessage
.getRecipients()
194 sendermail
= xMailMessage
.SenderAddress
195 sendername
= xMailMessage
.SenderName
196 subject
= xMailMessage
.Subject
197 ccrecipients
= xMailMessage
.getCcRecipients()
198 bccrecipients
= xMailMessage
.getBccRecipients()
200 print("PyMailSMTPService subject: " + subject
, file=sys
.stderr
)
201 print("PyMailSMTPService from: " + sendername
, file=sys
.stderr
)
202 print("PyMailSMTPService from: " + sendermail
, file=sys
.stderr
)
203 print("PyMailSMTPService send to: %s" % (recipients
,), file=sys
.stderr
)
205 attachments
= xMailMessage
.getAttachments()
209 content
= xMailMessage
.Body
210 flavors
= content
.getTransferDataFlavors()
213 "PyMailSMTPService flavors len: %d" % (len(flavors
),), file=sys
.stderr
216 # Use first flavor that's sane for an email body
217 for flavor
in flavors
:
219 flavor
.MimeType
.find("text/html") != -1
220 or flavor
.MimeType
.find("text/plain") != -1
224 "PyMailSMTPService mimetype is: " + flavor
.MimeType
,
227 textbody
= content
.getTransferData(flavor
)
230 mimeEncoding
= re
.sub(
231 "charset=.*", "charset=UTF-8", flavor
.MimeType
233 if mimeEncoding
.find("charset=UTF-8") == -1:
234 mimeEncoding
= mimeEncoding
+ "; charset=UTF-8"
235 textmsg
["Content-Type"] = mimeEncoding
236 textmsg
["MIME-Version"] = "1.0"
239 # it's a string, get it as utf-8 bytes
240 textbody
= textbody
.encode("utf-8")
242 # it's a bytesequence, get raw bytes
243 textbody
= textbody
.value
244 textbody
= textbody
.decode("utf-8")
247 textmsg
.set_payload(textbody
, c
)
252 msg
= MIMEMultipart()
258 hdr
= Header(sendername
, "utf-8")
259 hdr
.append("<" + sendermail
+ ">", "us-ascii")
260 msg
["Subject"] = subject
262 msg
["To"] = COMMASPACE
.join(recipients
)
263 if len(ccrecipients
):
264 msg
["Cc"] = COMMASPACE
.join(ccrecipients
)
265 if xMailMessage
.ReplyToAddress
!= "":
266 msg
["Reply-To"] = xMailMessage
.ReplyToAddress
268 mailerstring
= "LibreOffice via Caolan's mailmerge component"
270 ctx
= uno
.getComponentContext()
271 aConfigProvider
= ctx
.ServiceManager
.createInstance(
272 "com.sun.star.configuration.ConfigurationProvider"
274 prop
= uno
.createUnoStruct("com.sun.star.beans.PropertyValue")
275 prop
.Name
= "nodepath"
276 prop
.Value
= "/org.openoffice.Setup/Product"
277 aSettings
= aConfigProvider
.createInstanceWithArguments(
278 "com.sun.star.configuration.ConfigurationAccess", (prop
,)
281 aSettings
.getByName("ooName")
283 + aSettings
.getByName("ooSetupVersion")
284 + " via Caolan's mailmerge component"
289 msg
["X-Mailer"] = mailerstring
290 msg
["Date"] = formatdate(localtime
=True)
292 for attachment
in attachments
:
293 content
= attachment
.Data
294 flavors
= content
.getTransferDataFlavors()
296 ctype
= flavor
.MimeType
297 maintype
, subtype
= ctype
.split("/", 1)
298 msgattachment
= MIMEBase(maintype
, subtype
)
299 data
= content
.getTransferData(flavor
)
300 msgattachment
.set_payload(data
.value
)
301 encode_base64(msgattachment
)
302 fname
= attachment
.ReadableName
304 msgattachment
.add_header(
305 "Content-Disposition", "attachment", filename
=fname
308 msgattachment
.add_header(
309 "Content-Disposition", "attachment", filename
=("utf-8", "", fname
)
313 ("PyMailSMTPService attachmentheader: ", str(msgattachment
)),
317 msg
.attach(msgattachment
)
320 for key
in recipients
:
322 if len(ccrecipients
):
323 for key
in ccrecipients
:
325 if len(bccrecipients
):
326 for key
in bccrecipients
:
328 truerecipients
= uniquer
.keys()
332 ("PyMailSMTPService recipients are: ", truerecipients
), file=sys
.stderr
335 self
.server
.sendmail(sendermail
, truerecipients
, msg
.as_string())
338 class PyMailIMAPService(unohelper
.Base
, XMailService
):
339 def __init__(self
, ctx
):
342 self
.supportedtypes
= ("Insecure", "Ssl")
344 self
.connectioncontext
= None
345 self
.notify
= EventObject(self
)
347 print("PyMailIMAPService init", file=sys
.stderr
)
349 def addConnectionListener(self
, xListener
):
351 print("PyMailIMAPService addConnectionListener", file=sys
.stderr
)
352 self
.listeners
.append(xListener
)
354 def removeConnectionListener(self
, xListener
):
356 print("PyMailIMAPService removeConnectionListener", file=sys
.stderr
)
357 self
.listeners
.remove(xListener
)
359 def getSupportedConnectionTypes(self
):
361 print("PyMailIMAPService getSupportedConnectionTypes", file=sys
.stderr
)
362 return self
.supportedtypes
364 def connect(self
, xConnectionContext
, xAuthenticator
):
366 print("PyMailIMAPService connect", file=sys
.stderr
)
368 self
.connectioncontext
= xConnectionContext
369 server
= xConnectionContext
.getValueByName("ServerName")
371 print(server
, file=sys
.stderr
)
372 port
= int(xConnectionContext
.getValueByName("Port"))
374 print(port
, file=sys
.stderr
)
375 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
377 print(connectiontype
, file=sys
.stderr
)
378 tlscontext
= prepareTLSContext(self
, self
.ctx
, connectiontype
.upper() == "SSL")
379 print("BEFORE", file=sys
.stderr
)
380 if connectiontype
.upper() == "SSL":
381 self
.server
= imaplib
.IMAP4_SSL(server
, port
, ssl_context
=tlscontext
)
383 self
.server
= imaplib
.IMAP4(server
, port
)
384 print("AFTER", file=sys
.stderr
)
386 user
= xAuthenticator
.getUserName()
387 password
= xAuthenticator
.getPassword()
390 print("Logging in, username of: " + user
, file=sys
.stderr
)
391 self
.server
.login(user
, password
)
393 for listener
in self
.listeners
:
394 listener
.connected(self
.notify
)
396 def disconnect(self
):
398 print("PyMailIMAPService disconnect", file=sys
.stderr
)
402 for listener
in self
.listeners
:
403 listener
.disconnected(self
.notify
)
405 def isConnected(self
):
407 print("PyMailIMAPService isConnected", file=sys
.stderr
)
408 return self
.server
is not None
410 def getCurrentConnectionContext(self
):
412 print("PyMailIMAPService getCurrentConnectionContext", file=sys
.stderr
)
413 return self
.connectioncontext
416 class PyMailPOP3Service(unohelper
.Base
, XMailService
):
417 def __init__(self
, ctx
):
420 self
.supportedtypes
= ("Insecure", "Ssl")
422 self
.connectioncontext
= None
423 self
.notify
= EventObject(self
)
425 print("PyMailPOP3Service init", file=sys
.stderr
)
427 def addConnectionListener(self
, xListener
):
429 print("PyMailPOP3Service addConnectionListener", file=sys
.stderr
)
430 self
.listeners
.append(xListener
)
432 def removeConnectionListener(self
, xListener
):
434 print("PyMailPOP3Service removeConnectionListener", file=sys
.stderr
)
435 self
.listeners
.remove(xListener
)
437 def getSupportedConnectionTypes(self
):
439 print("PyMailPOP3Service getSupportedConnectionTypes", file=sys
.stderr
)
440 return self
.supportedtypes
442 def connect(self
, xConnectionContext
, xAuthenticator
):
444 print("PyMailPOP3Service connect", file=sys
.stderr
)
446 self
.connectioncontext
= xConnectionContext
447 server
= xConnectionContext
.getValueByName("ServerName")
449 print(server
, file=sys
.stderr
)
450 port
= int(xConnectionContext
.getValueByName("Port"))
452 print(port
, file=sys
.stderr
)
453 connectiontype
= xConnectionContext
.getValueByName("ConnectionType")
455 print(connectiontype
, file=sys
.stderr
)
456 tlscontext
= prepareTLSContext(self
, self
.ctx
, connectiontype
.upper() == "SSL")
457 print("BEFORE", file=sys
.stderr
)
458 if connectiontype
.upper() == "SSL":
459 self
.server
= poplib
.POP3_SSL(server
, port
, context
=tlscontext
)
461 tout
= xConnectionContext
.getValueByName("Timeout")
463 print(isinstance(tout
, int), file=sys
.stderr
)
464 if not isinstance(tout
, int):
465 tout
= _GLOBAL_DEFAULT_TIMEOUT
467 print("Timeout: " + str(tout
), file=sys
.stderr
)
468 self
.server
= poplib
.POP3(server
, port
, timeout
=tout
)
469 print("AFTER", file=sys
.stderr
)
471 user
= xAuthenticator
.getUserName()
472 password
= xAuthenticator
.getPassword()
474 print("Logging in, username of: " + user
, file=sys
.stderr
)
475 self
.server
.user(user
)
476 self
.server
.pass_(password
)
478 for listener
in self
.listeners
:
479 listener
.connected(self
.notify
)
481 def disconnect(self
):
483 print("PyMailPOP3Service disconnect", file=sys
.stderr
)
487 for listener
in self
.listeners
:
488 listener
.disconnected(self
.notify
)
490 def isConnected(self
):
492 print("PyMailPOP3Service isConnected", file=sys
.stderr
)
493 return self
.server
is not None
495 def getCurrentConnectionContext(self
):
497 print("PyMailPOP3Service getCurrentConnectionContext", file=sys
.stderr
)
498 return self
.connectioncontext
501 class PyMailServiceProvider(unohelper
.Base
, XMailServiceProvider
, XServiceInfo
):
502 def __init__(self
, ctx
):
504 print("PyMailServiceProvider init", file=sys
.stderr
)
507 def create(self
, aType
):
509 print("PyMailServiceProvider create with", aType
, file=sys
.stderr
)
511 return PyMailSMTPService(self
.ctx
)
513 return PyMailPOP3Service(self
.ctx
)
515 return PyMailIMAPService(self
.ctx
)
517 print("PyMailServiceProvider, unknown TYPE " + aType
, file=sys
.stderr
)
519 def getImplementationName(self
):
520 return g_providerImplName
522 def supportsService(self
, ServiceName
):
523 return g_ImplementationHelper
.supportsService(g_providerImplName
, ServiceName
)
525 def getSupportedServiceNames(self
):
526 return g_ImplementationHelper
.getSupportedServiceNames(g_providerImplName
)
529 class PyMailMessage(unohelper
.Base
, XMailMessage
):
531 self
, ctx
, sTo
="", sFrom
="", Subject
="", Body
=None, aMailAttachment
=None
534 print("PyMailMessage init", file=sys
.stderr
)
537 self
.recipients
= [sTo
]
538 self
.ccrecipients
= []
539 self
.bccrecipients
= []
540 self
.aMailAttachments
= []
541 if aMailAttachment
is not None:
542 self
.aMailAttachments
.append(aMailAttachment
)
544 self
.SenderName
, self
.SenderAddress
= parseaddr(sFrom
)
545 self
.ReplyToAddress
= sFrom
546 self
.Subject
= Subject
549 print("post PyMailMessage init", file=sys
.stderr
)
551 def addRecipient(self
, recipient
):
553 print("PyMailMessage.addRecipient: " + recipient
, file=sys
.stderr
)
554 self
.recipients
.append(recipient
)
556 def addCcRecipient(self
, ccrecipient
):
558 print("PyMailMessage.addCcRecipient: " + ccrecipient
, file=sys
.stderr
)
559 self
.ccrecipients
.append(ccrecipient
)
561 def addBccRecipient(self
, bccrecipient
):
563 print("PyMailMessage.addBccRecipient: " + bccrecipient
, file=sys
.stderr
)
564 self
.bccrecipients
.append(bccrecipient
)
566 def getRecipients(self
):
569 "PyMailMessage.getRecipients: " + str(self
.recipients
), file=sys
.stderr
571 return tuple(self
.recipients
)
573 def getCcRecipients(self
):
576 "PyMailMessage.getCcRecipients: " + str(self
.ccrecipients
),
579 return tuple(self
.ccrecipients
)
581 def getBccRecipients(self
):
584 "PyMailMessage.getBccRecipients: " + str(self
.bccrecipients
),
587 return tuple(self
.bccrecipients
)
589 def addAttachment(self
, aMailAttachment
):
591 print("PyMailMessage.addAttachment", file=sys
.stderr
)
592 self
.aMailAttachments
.append(aMailAttachment
)
594 def getAttachments(self
):
596 print("PyMailMessage.getAttachments", file=sys
.stderr
)
597 return tuple(self
.aMailAttachments
)
599 def getImplementationName(self
):
600 return g_messageImplName
602 def supportsService(self
, ServiceName
):
603 return g_ImplementationHelper
.supportsService(g_messageImplName
, ServiceName
)
605 def getSupportedServiceNames(self
):
606 return g_ImplementationHelper
.getSupportedServiceNames(g_messageImplName
)
609 g_ImplementationHelper
.addImplementation(
610 PyMailServiceProvider
,
612 ("com.sun.star.mail.MailServiceProvider",),
614 g_ImplementationHelper
.addImplementation(
617 ("com.sun.star.mail.MailMessage",),
620 # vim: set shiftwidth=4 softtabstop=4 expandtab: