Add MMS sending capacilities #1

Merged
alex merged 12 commits from dev into master 2021-05-21 05:57:05 +00:00
2 changed files with 105 additions and 59 deletions
Showing only changes of commit 2446c76b6a - Show all commits

View file

@ -14,12 +14,11 @@ in case the mbox is locked by another process the output could be found in :
- python3
- python-messaging (pip install python-messaging)
- marrow.mailer (pip install marrow.mailer)
### setup
Install the dependency and mms2mail:
```
pip install --user marrow-mailer
sudo apt-get install python3
pip install --user python-messaging
mkdir -p ~/.local/bin
@ -47,7 +46,8 @@ attach_mms = false ; whether to attach the full mms binary file
## usage
```
mms2mail [-h] [-d | -f FILES [FILES ...]] [--delete] [--disable-dbus] [--force-read] [--force-unlock]
mms2mail [-h] [-d | -f FILES [FILES ...]] [--delete] [--force-read]
[--force-unlock] [-l {critical,error,warning,info,debug}]
optional arguments:
-h, --help show this help message and exit
@ -55,7 +55,9 @@ optional arguments:
-f FILES [FILES ...], --file FILES [FILES ...]
Parse specified mms files and quit
--delete Ask mmsd to delete the converted MMS
--disable-dbus disable dbus request to mmsd
--force-read Force conversion even if MMS is marked as read
--force-unlock BEWARE COULD LEAD TO WHOLE MBOX CORRUPTION Force unlocking the mbox after a few minutes /!\
--force-unlock BEWARE COULD LEAD TO WHOLE MBOX CORRUPTION Force unlocking the
mbox after a few minutes /!\
-l {critical,error,warning,info,debug}, --logging {critical,error,warning,info,debug}
Define the logger output level
```

138
mms2mail
View file

@ -29,17 +29,18 @@ import getpass
import socket
import mimetypes
import time
import logging
from pathlib import Path
from messaging.mms.message import MMSMessage
import mailbox
from marrow.mailer import Message
import email
from gi.repository import GLib
import dbus
import dbus.mainloop.glib
log = __import__('logging').getLogger(__name__)
log = logging.getLogger(__name__)
class MMS2Mail:
@ -51,7 +52,7 @@ class MMS2Mail:
"""
def __init__(self, delete=False, force_read=False,
disable_dbus=False, force_unlock=False):
force_unlock=False):
"""
Return class instance.
@ -69,7 +70,6 @@ class MMS2Mail:
"""
self.delete = delete
self.force_read = force_read
self.disable_dbus = disable_dbus
self.force_unlock = force_unlock
self.config = configparser.ConfigParser()
self.config.read(f"{Path.home()}/.mms/modemmanager/mms2mail.ini")
@ -98,37 +98,35 @@ class MMS2Mail:
:param path: the mms filesystem path
:type path: str
:rtype bool
:return the mms status or None
:rtype str
"""
# Check for mmsd data file
if not Path(f"{path}").is_file():
log.error("MMS file not found : aborting")
return False
return None
# Check for mmsd status file
status = configparser.ConfigParser()
if not Path(f"{path}.status").is_file():
log.error("MMS status file not found : aborting")
return False
return None
status.read_file(open(f"{path}.status"))
# Allow only incoming MMS for the time beeing
if not (status['info']['state'] == 'downloaded' or
status['info']['state'] == 'received'):
log.error("Outgoing MMS : aborting")
return False
if not (self.force_read or not status.getboolean('info', 'read')):
log.error("Already converted MMS : aborting")
return False
return True
return None
return status['info']['state']
def message_added(self, name, value, member, path, interface):
"""Trigger conversion on MessageAdded signal."""
if value['Status'] == 'downloaded' or value['Status'] == 'received':
log.debug(f"New incoming MMS found ({name.split('/')[-1]})")
self.convert(value['Attachments'][0][2], name)
self.convert(path=value['Attachments'][0][2], dbus_path=name,
properties=value)
else:
log.debug(f"New outgoing MMS found ({name.split('/')[-1]})")
def convert(self, path, dbus_path=None):
def convert(self, path, dbus_path=None, properties=None):
"""
Convert a provided mms file to a mail stored in a mbox.
@ -139,7 +137,8 @@ class MMS2Mail:
:type dbus_path: str
"""
# Check if the provided file present
if not self.check_mms(path):
status = self.check_mms(path)
if not status:
log.error("MMS file not convertible.")
return
# Generate its dbus path, for future operation (mark as read, delete)
@ -147,48 +146,83 @@ class MMS2Mail:
dbus_path = f"/org/ofono/mms/modemmanager/{path.split('/')[-1]}"
mms = MMSMessage.from_file(path)
message = Message()
message = email.message.EmailMessage()
# Generate Mail Headers
mms_from, mms_from_type = mms.headers.get('From',
'unknown/undef').split('/')
message.author = f"{mms_from}@{self.domain}"
mms_from, mms_from_type = mms.headers.get('To',
'unknown/undef').split('/')
message.to = f"{self.user}@{self.domain}"
mms_h_from = mms.headers.get('From', 'unknown/undef')
log.debug(f"MMS[From]: {mms_h_from}")
if 'not inserted' in mms_h_from:
mms_h_from = 'unknown/undef'
mms_from, mms_from_type = mms_h_from.split('/')
message['From'] = f"{mms_from}@{self.domain}"
mms_h_to = mms.headers.get('To', 'unknown/undef')
log.debug(f"MMS[To]: {mms_h_to}")
if 'not inserted' in mms_h_to:
mms_h_to = 'unknown/undef'
mms_to, mms_to_type = mms_h_to.split('/')
message['To'] = f"{mms_to}@{self.domain}"
# Get other recipients from dbus signal
# https://github.com/pmarti/python-messaging/issues/49
if properties:
cc = ""
for r in properties['Recipients']:
if mms_to in r:
continue
log.debug(f'MMS/MAIL CC : {r}')
cc += f"{r}@{self.domain},"
if cc:
cc = cc[:-1]
message['CC'] = cc
if 'Subject' in mms.headers and mms.headers['Subject']:
message.subject = mms.headers['Subject']
message['Subject'] = mms.headers['Subject']
else:
message.subject = f"MMS from {mms_from}"
if status == 'sent' or status == 'draft':
message['Subject'] = f"MMS to {mms_to}"
else:
message['Subject'] = f"MMS from {mms_from}"
if 'Date' in mms.headers and mms.headers['Date']:
message.date = mms.headers['Date']
message['Date'] = mms.headers['Date']
# Recopy MMS HEADERS
for header in mms.headers:
message.headers.append((f"X-MMS-{header}",
f"{mms.headers[header]}"))
message.add_header(f"X-MMS-{header}", f"{mms.headers[header]}")
message.plain = " "
message.preamble = "This mail is converted from a MMS."
body = ""
data_id = 1
attachments = []
for data_part in mms.data_parts:
datacontent = data_part.headers['Content-Type']
if datacontent is not None:
maintype, subtype = datacontent[0].split('/', 1)
if 'text/plain' in datacontent[0]:
encoding = datacontent[1].get('Charset', 'utf-8')
plain = data_part.data.decode(encoding)
message.plain += plain + '\n'
body += data_part.data.decode(encoding) + '\n'
continue
extension = str(mimetypes.guess_extension(datacontent[0]))
filename = datacontent[1].get('Name', str(data_id))
message.attach(filename + extension, data_part.data)
attachments.append([data_part.data, maintype,
subtype, filename + extension])
data_id = data_id + 1
if body:
message.set_content(body)
for a in attachments:
message.add_attachment(a[0],
maintype=a[1],
subtype=a[2],
filename=a[3])
# Add MMS binary file, for debugging purpose or reparsing in the future
if self.attach_mms:
message.attach(path, None, None, None, False, path.split('/')[-1])
with open(path, 'rb') as fp:
message.add_attachment(fp.read(),
maintype='application',
subtype='octet-stream',
filename=path.split('/')[-1])
# Write the mail in case of mbox lock retry for 5 minutes
# Ultimately write in an mbox in the home folder
@ -197,7 +231,7 @@ class MMS2Mail:
try:
# self.mailer.send(message)
self.mailbox.lock()
self.mailbox.add(mailbox.mboxMessage(str(message)))
self.mailbox.add(mailbox.mboxMessage(message))
self.mailbox.flush()
self.mailbox.unlock()
break
@ -228,10 +262,8 @@ class MMS2Mail:
else:
time.sleep(5)
# Ask mmsd to mark message as read and delete it
if self.disable_dbus:
return
if properties:
self.dbus.mark_mms_read(dbus_path)
if self.delete:
self.dbus.delete_mms(dbus_path)
@ -267,7 +299,7 @@ class DbusMMSd():
:param dbus_path: the mms dbus path
:type dbus_path: str
"""
message = dbus.Interface(self.bus.get_object('org.ofono.mms',
message = dbus.proxies.Interface(self.bus.get_object('org.ofono.mms',
dbus_path),
'org.ofono.mms.Message')
log.debug(f"Marking MMS as read {dbus_path}")
@ -282,7 +314,7 @@ class DbusMMSd():
"""
if self.disable_dbus:
return None
message = dbus.Interface(self.bus.get_object('org.ofono.mms',
message = dbus.proxies.Interface(self.bus.get_object('org.ofono.mms',
dbus_path),
'org.ofono.mms.Message')
log.debug(f"Deleting MMS {dbus_path}")
@ -323,9 +355,6 @@ def main():
help="Parse specified mms files and quit", dest='files')
parser.add_argument('--delete', action='store_true', dest='delete',
help="Ask mmsd to delete the converted MMS")
parser.add_argument('--disable-dbus', action='store_true',
dest='disable_dbus',
help="disable dbus request to mmsd")
parser.add_argument('--force-read', action='store_true',
dest='force_read', help="Force conversion even if MMS \
is marked as read")
@ -334,16 +363,31 @@ def main():
WHOLE MBOX CORRUPTION \
Force unlocking the mbox \
after a few minutes /!\\")
parser.add_argument('-l', '--logging', dest='log_level', default='warning',
choices=['critical', 'error', 'warning',
'info', 'debug'],
help='Define the logger output level'
)
args = parser.parse_args()
log.setLevel(args.log_level.upper())
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(log_format)
ch.setFormatter(formatter)
log.addHandler(ch)
d = DbusMMSd()
m = MMS2Mail(args.delete, args.force_read,
args.disable_dbus, args.force_unlock)
m = MMS2Mail(delete=args.delete, force_read=args.force_read,
force_unlock=args.force_unlock)
m.set_dbus(d)
if args.files:
for mms_file in args.files:
m.convert(mms_file)
m.convert(path=mms_file)
return
elif args.watcher:
log.info("Starting mms2mail in daemon mode")
d.set_mms2mail(m)