From fb8c22a535e0a22184328fc8a9eebf2dcec6cbdf Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 7 May 2021 12:12:08 +0200 Subject: [PATCH 01/12] [feat] Remove Filesystem based watcher --- mms2mail | 109 ++++++++++++----------------------------------- mms2mail.service | 2 +- 2 files changed, 29 insertions(+), 82 deletions(-) diff --git a/mms2mail b/mms2mail index 69c35f6..a8fa85c 100755 --- a/mms2mail +++ b/mms2mail @@ -25,8 +25,6 @@ if sys.version_info[0] == 3 and sys.version_info[1] > 8: import argparse import configparser -import re -import time import getpass import socket import mimetypes @@ -38,8 +36,6 @@ from marrow.mailer import Mailer, Message from gi.repository import GLib import dbus import dbus.mainloop.glib -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler class MMS2Mail: @@ -214,74 +210,21 @@ class MMS2Mail: self.mailer.stop() -class FSWatcher: - """ - Use OS filesystem notification to watch for new MMS (DEPRECATED). - - Events are send to the FSHandler class - """ - - # Path to modemmanager storage - mms_folder = f"{Path.home()}/.mms/modemmanager" - - def __init__(self): - """Construct an instance.""" - self.observer = Observer() - self.patternold = re.compile('[0-9A-F]{40}$') - self.pattern = re.compile('[0-9a-f]{36}$') - - def is_mmsd_mms_file(self, path): - """ - Test if the provided file seems to be a mms file created by mmsd. - - :param path: the mms filesystem path - :type path: str - - :rtype boolean - :return: the test result - """ - if self.pattern.search(path) or self.patternold.search(path): - return True - else: - return False - - def run(self): - """Run the watcher mainloop.""" - event_handler = FSHandler() - self.observer.schedule(event_handler, self.mms_folder, recursive=False) - self.observer.start() - try: - while True: - time.sleep(5) - finally: - self.observer.stop() - self.observer.join() - - -class FSHandler(FileSystemEventHandler): - """Handle the FSWatcher event.""" - - @staticmethod - def on_any_event(event): - """Trigger conversion on event by the FSWatcher.""" - if event.is_directory: - return None - elif event.event_type == 'created' or event.event_type == 'modified': - if w.is_mmsd_mms_file(event.src_path): - print(f"New MMS found : {event.src_path}.", file=sys.stderr) - m.convert(event.src_path) - elif event.event_type == 'moved': - if w.is_mmsd_mms_file(event.dest_path): - print(f"New MMS found : {event.dest_path}.", file=sys.stderr) - m.convert(event.dest_path) - - class DbusWatcher(): """Use DBus Signal notification to watch for new MMS.""" + def __init__(self, mms2mail): + """ + Return a DBusWatcher instance. + + :param mms2mail: An mms2mail instance to convert new mms + :type mms2mail: mms2mail() + """ + self.mms2mail = mms2mail + def run(self): """Run the watcher mainloop.""" - bus = m.get_bus() + bus = self.mms2mail.get_bus() bus.add_signal_receiver(self.message_added, bus_name="org.ofono.mms", signal_name="MessageAdded", @@ -289,25 +232,29 @@ class DbusWatcher(): path_keyword="path", interface_keyword="interface") mainloop = GLib.MainLoop() - mainloop.run() + print("Starting DBus watcher mainloop") + try: + mainloop.run() + except KeyboardInterrupt: + print("Stopping DBus watcher mainloop") + mainloop.quit() def message_added(self, name, value, member, path, interface): """Trigger conversion on MessageAdded signal.""" if value['Status'] == 'downloaded' or value['Status'] == 'received': print(f"New incoming MMS found ({name.split('/')[-1]})") - m.convert(value['Attachments'][0][2], name) + self.mms2mail.convert(value['Attachments'][0][2], name) else: print(f"New outgoing MMS found ({name.split('/')[-1]})") -if __name__ == '__main__': +def main(): + """Run the different functions handling mms and mail.""" parser = argparse.ArgumentParser() mode = parser.add_mutually_exclusive_group() mode.add_argument("-d", "--daemon", - help="Use dbus signal from mmsd by default but can also \ - watch mmsd storage folder (useful for mmsd < 1.0)", - nargs="?", default="dbus", - choices=['dbus', 'filesystem'], dest='watcher') + help="Use dbus signal from mmsd to trigger conversion", + action='store_true', dest='watcher') mode.add_argument("-f", "--file", nargs='+', help="Parse specified mms files and quit", dest='files') parser.add_argument('--delete', action='store_true', dest='delete', @@ -322,13 +269,13 @@ if __name__ == '__main__': if args.files: for mms_file in args.files: m.convert(mms_file) - elif args.watcher == 'dbus': - print("Starting mms2mail in daemon mode with dbus watcher") - w = DbusWatcher() - w.run() - elif args.watcher == 'filesystem': - print("Starting mms2mail in daemon mode with filesystem watcher") - w = FSWatcher() + elif args.watcher: + print("Starting mms2mail in daemon mode") + w = DbusWatcher(m) w.run() else: parser.print_help() + + +if __name__ == '__main__': + main() diff --git a/mms2mail.service b/mms2mail.service index 82703d8..4f24954 100644 --- a/mms2mail.service +++ b/mms2mail.service @@ -3,7 +3,7 @@ Description=Multimedia Messaging Service to Mail converter Daemon After=mmsd.service [Service] -ExecStart=python3 %h/.local/bin/mms2mail -d dbus +ExecStart=python3 %h/.local/bin/mms2mail -d Restart=on-failure RestartSec=10s -- 2.40.1 From 815b27ae23761aa473841dffa6c01fc53486c3fe Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 8 May 2021 10:29:07 +0200 Subject: [PATCH 02/12] [fix] Better handling mbox lock - use only marrow.mailer Message class - use python logging --- README.md | 18 +++++---- mms2mail | 113 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 98 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 7e32662..a6609a1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ # mms2mail -Convert MMSd MMS file to mbox. +Convert mmsd MMS file to mbox. + +mms2mail can convert mms in batch mode, or wait for new mms via a dbus signal +sent by mmsd. +By default it store them in the current user mbox (```/var/mail/$USER```) +in case the mbox is locked by another process the output could be found in : +```$HOME/.mms/failsafembox``` ## installation ### dependency - python3 - - python3-watchdog (pip install watchdog) - python-messaging (pip install python-messaging) - marrow.mailer (pip install marrow.mailer) ### setup Install the dependency and mms2mail: ``` -pip install --user watchdog # or sudo apt install python3-watchdog pip install --user marrow-mailer pip install --user python-messaging @@ -42,16 +46,16 @@ attach_mms = false ; whether to attach the full mms binary file ``` ## usage - ``` -mms2mail [-h] [-d [{dbus,filesystem}] | -f FILES [FILES ...]] [--delete] [--force-read] +mms2mail [-h] [-d | -f FILES [FILES ...]] [--delete] [--disable-dbus] [--force-read] [--force-unlock] optional arguments: -h, --help show this help message and exit - -d [{dbus,filesystem}], --daemon [{dbus,filesystem}] - Use dbus signal from mmsd by default but can also watch mmsd storage folder (useful for mmsd < 1.0) + -d, --daemon Use dbus signal from mmsd to trigger conversion -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 /!\ ``` \ No newline at end of file diff --git a/mms2mail b/mms2mail index a8fa85c..3f4c2e8 100755 --- a/mms2mail +++ b/mms2mail @@ -28,15 +28,19 @@ import configparser import getpass import socket import mimetypes +import time from pathlib import Path from messaging.mms.message import MMSMessage -from marrow.mailer import Mailer, Message +import mailbox +from marrow.mailer import Message from gi.repository import GLib import dbus import dbus.mainloop.glib +log = __import__('logging').getLogger(__name__) + class MMS2Mail: """ @@ -46,7 +50,8 @@ class MMS2Mail: Mail support is provided by marrow.mailer """ - def __init__(self, delete=False, force_read=False): + def __init__(self, delete=False, force_read=False, + disable_dbus=False, force_unlock=False): """ Return class instance. @@ -55,9 +60,17 @@ class MMS2Mail: :param force_read: force converting an already read MMS (batch mode) :type force_read: bool + + :param disable_dbus: Disable sending dbus commands to mmsd (batch mode) + :type disable_dbus: bool + + :param force_unlock: Force mbox unlocking after a few minutes + :type force_unlock: bool """ 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") self.attach_mms = self.config.getboolean('mail', 'attach_mms', @@ -67,9 +80,9 @@ class MMS2Mail: self.user = self.config.get('mail', 'user', fallback=getpass.getuser()) mbox_file = self.config.get('mail', 'mailbox', fallback=f"/var/mail/{self.user}") - self.mailer = Mailer({'manager.use': 'immediate', - 'transport.use': 'mbox', - 'transport.file': mbox_file}) + self.mailbox = mailbox.mbox(mbox_file) + if self.disable_dbus: + return dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) self.bus = dbus.SessionBus() @@ -80,6 +93,8 @@ class MMS2Mail: :rtype dbus.SessionBus() :return: an active SessionBus """ + if self.disable_dbus: + return None return self.bus def mark_mms_read(self, dbus_path): @@ -89,10 +104,12 @@ class MMS2Mail: :param dbus_path: the mms dbus path :type dbus_path: str """ + if self.disable_dbus: + return None message = dbus.Interface(self.bus.get_object('org.ofono.mms', dbus_path), 'org.ofono.mms.Message') - print(f"Marking MMS as read {dbus_path}", file=sys.stderr) + log.debug(f"Marking MMS as read {dbus_path}") message.MarkRead() def delete_mms(self, dbus_path): @@ -102,10 +119,12 @@ class MMS2Mail: :param dbus_path: the mms dbus path :type dbus_path: str """ + if self.disable_dbus: + return None message = dbus.Interface(self.bus.get_object('org.ofono.mms', dbus_path), 'org.ofono.mms.Message') - print(f"Deleting MMS {dbus_path}", file=sys.stderr) + log.debug(f"Deleting MMS {dbus_path}") message.Delete() def check_mms(self, path): @@ -118,21 +137,21 @@ class MMS2Mail: """ # Check for mmsd data file if not Path(f"{path}").is_file(): - print("MMS file not found : aborting", file=sys.stderr) + log.error("MMS file not found : aborting") return False # Check for mmsd status file status = configparser.ConfigParser() if not Path(f"{path}.status").is_file(): - print("MMS status file not found : aborting", file=sys.stderr) + log.error("MMS status file not found : aborting") return False 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'): - print("Outgoing MMS : aborting", file=sys.stderr) + log.error("Outgoing MMS : aborting") return False if not (self.force_read or not status.getboolean('info', 'read')): - print("Already converted MMS : aborting", file=sys.stderr) + log.error("Already converted MMS : aborting") return False return True @@ -148,7 +167,7 @@ class MMS2Mail: """ # Check if the provided file present if not self.check_mms(path): - print("MMS file not convertible.", file=sys.stderr) + log.error("MMS file not convertible.") return # Generate its dbus path, for future operation (mark as read, delete) if not dbus_path: @@ -156,7 +175,6 @@ class MMS2Mail: mms = MMSMessage.from_file(path) - self.mailer.start() message = Message() # Generate Mail Headers @@ -190,7 +208,7 @@ class MMS2Mail: plain = data_part.data.decode(encoding) message.plain += plain + '\n' continue - extension = mimetypes.guess_extension(datacontent[0]) + extension = str(mimetypes.guess_extension(datacontent[0])) filename = datacontent[1].get('Name', str(data_id)) message.attach(filename + extension, data_part.data) data_id = data_id + 1 @@ -199,16 +217,50 @@ class MMS2Mail: if self.attach_mms: message.attach(path, None, None, None, False, path.split('/')[-1]) - # Creating an empty file stating the mms as been converted - self.mark_mms_read(dbus_path) + # Write the mail in case of mbox lock retry for 5 minutes + # Ultimately write in an mbox in the home folder + end_time = time.time() + (5 * 60) + while True: + try: + # self.mailer.send(message) + self.mailbox.lock() + self.mailbox.add(mailbox.mboxMessage(str(message))) + self.mailbox.flush() + self.mailbox.unlock() + break + except (mailbox.ExternalClashError, FileExistsError) as e: + log.warn(f"Exception Mbox lock : {e}") + if time.time() > end_time: + if self.force_unlock: + log.error("Force removing lock") + self.mailbox.unlock() + else: + fs_mbox_path = f"{Path.home()}/.mms/failsafembox" + fs_mbox = mailbox.mbox(fs_mbox_path) + log.warning(f"Writing in internal mbox {fs_mbox_path}") + try: + fs_mbox.unlock() + fs_mbox.lock() + fs_mbox.add(mailbox.mboxMessage(str(message))) + fs_mbox.flush() + fs_mbox.unlock() + break + except (mailbox.ExternalClashError, + FileExistsError) as e: + log.error(f"Failsafe Mbox error : {e}") + log.error(f"MMS cannot be written to any mbox : \ + {path.split('/')[-1]}") + finally: + break + else: + time.sleep(5) + + # Ask mmsd to mark message as read and delete it + self.mark_mms_read(dbus_path) if self.delete: self.delete_mms(dbus_path) - # Write the mail - self.mailer.send(message) - self.mailer.stop() - class DbusWatcher(): """Use DBus Signal notification to watch for new MMS.""" @@ -232,20 +284,20 @@ class DbusWatcher(): path_keyword="path", interface_keyword="interface") mainloop = GLib.MainLoop() - print("Starting DBus watcher mainloop") + log.info("Starting DBus watcher mainloop") try: mainloop.run() except KeyboardInterrupt: - print("Stopping DBus watcher mainloop") + log.info("Stopping DBus watcher mainloop") mainloop.quit() def message_added(self, name, value, member, path, interface): """Trigger conversion on MessageAdded signal.""" if value['Status'] == 'downloaded' or value['Status'] == 'received': - print(f"New incoming MMS found ({name.split('/')[-1]})") + log.debug(f"New incoming MMS found ({name.split('/')[-1]})") self.mms2mail.convert(value['Attachments'][0][2], name) else: - print(f"New outgoing MMS found ({name.split('/')[-1]})") + log.debug(f"New outgoing MMS found ({name.split('/')[-1]})") def main(): @@ -259,18 +311,27 @@ 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") + parser.add_argument('--force-unlock', action='store_true', + dest='force_unlock', help="BEWARE COULD LEAD TO \ + WHOLE MBOX CORRUPTION \ + Force unlocking the mbox \ + after a few minutes /!\\") args = parser.parse_args() - m = MMS2Mail(args.delete, args.force_read) + m = MMS2Mail(args.delete, args.force_read, + args.disable_dbus, args.force_unlock) if args.files: for mms_file in args.files: m.convert(mms_file) elif args.watcher: - print("Starting mms2mail in daemon mode") + log.info("Starting mms2mail in daemon mode") w = DbusWatcher(m) w.run() else: -- 2.40.1 From bcfe2af140b4436264e92f5a66e7821d8faf1aae Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 9 May 2021 19:34:50 +0200 Subject: [PATCH 03/12] [refactor] Move all dbus action in a specific class --- mms2mail | 141 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 63 deletions(-) diff --git a/mms2mail b/mms2mail index 3f4c2e8..75fa5c0 100755 --- a/mms2mail +++ b/mms2mail @@ -81,51 +81,16 @@ class MMS2Mail: mbox_file = self.config.get('mail', 'mailbox', fallback=f"/var/mail/{self.user}") self.mailbox = mailbox.mbox(mbox_file) - if self.disable_dbus: - return - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - self.bus = dbus.SessionBus() + self.dbus = None - def get_bus(self): + def set_dbus(self, dbusmmsd): """ Return the DBus SessionBus. - :rtype dbus.SessionBus() - :return: an active SessionBus + :param dbusmmsd: The DBus MMSd abstraction class + :type dbusmmsd: DbusMMSd() """ - if self.disable_dbus: - return None - return self.bus - - def mark_mms_read(self, dbus_path): - """ - Ask mmsd to mark the mms as read. - - :param dbus_path: the mms dbus path - :type dbus_path: str - """ - if self.disable_dbus: - return None - message = dbus.Interface(self.bus.get_object('org.ofono.mms', - dbus_path), - 'org.ofono.mms.Message') - log.debug(f"Marking MMS as read {dbus_path}") - message.MarkRead() - - def delete_mms(self, dbus_path): - """ - Ask mmsd to delete the mms. - - :param dbus_path: the mms dbus path - :type dbus_path: str - """ - if self.disable_dbus: - return None - message = dbus.Interface(self.bus.get_object('org.ofono.mms', - dbus_path), - 'org.ofono.mms.Message') - log.debug(f"Deleting MMS {dbus_path}") - message.Delete() + self.dbus = dbusmmsd def check_mms(self, path): """ @@ -155,6 +120,14 @@ class MMS2Mail: return False return True + 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) + else: + log.debug(f"New outgoing MMS found ({name.split('/')[-1]})") + def convert(self, path, dbus_path=None): """ Convert a provided mms file to a mail stored in a mbox. @@ -257,15 +230,17 @@ class MMS2Mail: time.sleep(5) # Ask mmsd to mark message as read and delete it - self.mark_mms_read(dbus_path) + if self.disable_dbus: + return + self.dbus.mark_mms_read(dbus_path) if self.delete: - self.delete_mms(dbus_path) + self.dbus.delete_mms(dbus_path) -class DbusWatcher(): - """Use DBus Signal notification to watch for new MMS.""" +class DbusMMSd(): + """Use DBus communication with mmsd.""" - def __init__(self, mms2mail): + def __init__(self, mms2mail=None): """ Return a DBusWatcher instance. @@ -273,16 +248,61 @@ class DbusWatcher(): :type mms2mail: mms2mail() """ self.mms2mail = mms2mail + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + self.bus = dbus.SessionBus() + + def set_mms2mail(self, mms2mail): + """ + Set mms2mail instance handling dbus event. + + :param mms2mail: An mms2mail instance to convert new mms + :type mms2mail: mms2mail() + """ + self.mms2mail = mms2mail + + def mark_mms_read(self, dbus_path): + """ + Ask mmsd to mark the mms as read. + + :param dbus_path: the mms dbus path + :type dbus_path: str + """ + message = dbus.Interface(self.bus.get_object('org.ofono.mms', + dbus_path), + 'org.ofono.mms.Message') + log.debug(f"Marking MMS as read {dbus_path}") + message.MarkRead() + + def delete_mms(self, dbus_path): + """ + Ask mmsd to delete the mms. + + :param dbus_path: the mms dbus path + :type dbus_path: str + """ + if self.disable_dbus: + return None + message = dbus.Interface(self.bus.get_object('org.ofono.mms', + dbus_path), + 'org.ofono.mms.Message') + log.debug(f"Deleting MMS {dbus_path}") + message.Delete() + + def add_signal_receiver(self): + """Add a signal receiver to the current bus.""" + if self.mms2mail: + self.bus.add_signal_receiver(self.mms2mail.message_added, + bus_name="org.ofono.mms", + signal_name="MessageAdded", + member_keyword="member", + path_keyword="path", + interface_keyword="interface") + return True + else: + return False def run(self): - """Run the watcher mainloop.""" - bus = self.mms2mail.get_bus() - bus.add_signal_receiver(self.message_added, - bus_name="org.ofono.mms", - signal_name="MessageAdded", - member_keyword="member", - path_keyword="path", - interface_keyword="interface") + """Run the dbus mainloop.""" mainloop = GLib.MainLoop() log.info("Starting DBus watcher mainloop") try: @@ -291,14 +311,6 @@ class DbusWatcher(): log.info("Stopping DBus watcher mainloop") mainloop.quit() - 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.mms2mail.convert(value['Attachments'][0][2], name) - else: - log.debug(f"New outgoing MMS found ({name.split('/')[-1]})") - def main(): """Run the different functions handling mms and mail.""" @@ -324,18 +336,21 @@ def main(): after a few minutes /!\\") args = parser.parse_args() + d = DbusMMSd() m = MMS2Mail(args.delete, args.force_read, args.disable_dbus, args.force_unlock) + m.set_dbus(d) if args.files: for mms_file in args.files: m.convert(mms_file) elif args.watcher: log.info("Starting mms2mail in daemon mode") - w = DbusWatcher(m) - w.run() + d.set_mms2mail(m) + d.add_signal_receiver() else: parser.print_help() + d.run() if __name__ == '__main__': -- 2.40.1 From 2446c76b6a6cdf2a6e67cbce2d6038481cba78e8 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 10 May 2021 10:30:37 +0200 Subject: [PATCH 04/12] [feat] Remove marrow.mailer --- README.md | 12 +++-- mms2mail | 152 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 105 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index a6609a1..b0eec5a 100644 --- a/README.md +++ b/README.md @@ -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 ``` \ No newline at end of file diff --git a/mms2mail b/mms2mail index 75fa5c0..24ec2e9 100755 --- a/mms2mail +++ b/mms2mail @@ -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,13 +262,11 @@ class MMS2Mail: else: time.sleep(5) - # Ask mmsd to mark message as read and delete it - if self.disable_dbus: - return - self.dbus.mark_mms_read(dbus_path) - if self.delete: - self.dbus.delete_mms(dbus_path) + if properties: + self.dbus.mark_mms_read(dbus_path) + if self.delete: + self.dbus.delete_mms(dbus_path) class DbusMMSd(): @@ -267,9 +299,9 @@ class DbusMMSd(): :param dbus_path: the mms dbus path :type dbus_path: str """ - message = dbus.Interface(self.bus.get_object('org.ofono.mms', - dbus_path), - 'org.ofono.mms.Message') + 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}") message.MarkRead() @@ -282,9 +314,9 @@ class DbusMMSd(): """ if self.disable_dbus: return None - message = dbus.Interface(self.bus.get_object('org.ofono.mms', - dbus_path), - 'org.ofono.mms.Message') + message = dbus.proxies.Interface(self.bus.get_object('org.ofono.mms', + dbus_path), + 'org.ofono.mms.Message') log.debug(f"Deleting MMS {dbus_path}") message.Delete() @@ -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) -- 2.40.1 From 06a5e828679fc93e6c6a1dab59521acced0c1066 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 10 May 2021 10:33:33 +0200 Subject: [PATCH 05/12] [fix] Remove marrow.mailer dirtyfix --- mms2mail | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/mms2mail b/mms2mail index 24ec2e9..dc5e1bd 100755 --- a/mms2mail +++ b/mms2mail @@ -1,28 +1,5 @@ #!/usr/bin/python3 """An mms to mail converter for mmsd.""" -# upstream bug dirty fix -# https://github.com/marrow/mailer/issues/87#issuecomment-689586587 -import sys -if sys.version_info[0] == 3 and sys.version_info[1] > 7: - sys.modules["cgi.parse_qsl"] = None -# upstream bug dirty fix -# https://github.com/marrow/mailer/issues/87#issuecomment-713319548 -import base64 -if sys.version_info[0] == 3 and sys.version_info[1] > 8: - def encodestring(value): - """ - Encode string in base64. - - :param value: the string to encode. - :type value: str - - :rtype str - :return: the base64 encoded string - """ - return base64.b64encode(value) - base64.encodestring = encodestring -# end bugfix - import argparse import configparser import getpass -- 2.40.1 From cc30d699120e2edebc4d8e3f4392f2e5ec031594 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 11 May 2021 16:52:27 +0200 Subject: [PATCH 06/12] [feat] SMTP server to send MMS --- README.md | 47 +++++++--- TODO.md | 11 +++ mms2mail | 251 ++++++++++++++++++++++++++++++++++++++++++++++----- mms2mail.ini | 4 + 4 files changed, 278 insertions(+), 35 deletions(-) create mode 100644 TODO.md diff --git a/README.md b/README.md index b0eec5a..0240682 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,29 @@ # mms2mail -Convert mmsd MMS file to mbox. +Mail bridge for mmsd. -mms2mail can convert mms in batch mode, or wait for new mms via a dbus signal -sent by mmsd. -By default it store them in the current user mbox (```/var/mail/$USER```) -in case the mbox is locked by another process the output could be found in : -```$HOME/.mms/failsafembox``` +mms2mail: +* convert incoming mms from mmsd to mail and store it in unix mbox. +* provide a smtp server converting mail to mms with mmsd. + +By default: +* store mails in the current user mbox (```/var/mail/$USER```) + * in case the mbox is locked by another process the output could be found in ```$HOME/.mms/failsafembox``` +* listen on localhost port 2525 for mail ## installation ### dependency - python3 + - python3-aiosmtpd - python-messaging (pip install python-messaging) ### setup -Install the dependency and mms2mail: +Install the dependency and mms2mail (on debian based distribution): ``` sudo apt-get install python3 +sudo apt-get install python3-aiosmtpd pip install --user python-messaging mkdir -p ~/.local/bin @@ -42,22 +47,38 @@ mailbox = /var/mail/$USER ; the mailbox where mms are appended user = $USER ; the user account specified as recipient domain = $HOSTNAME ; the domain part appended to phone number and user attach_mms = false ; whether to attach the full mms binary file + +[smtp] +hostname = localhost +port = 2525 ``` ## usage + +### reference ``` -mms2mail [-h] [-d | -f FILES [FILES ...]] [--delete] [--force-read] - [--force-unlock] [-l {critical,error,warning,info,debug}] + mms2mail [-h] [-d | -f FILES [FILES ...]] [--disable-smtp] [--disable-mms-delivery] [--delete] [--force-read] [--force-unlock] [-l {critical,error,warning,info,debug}] optional arguments: -h, --help show this help message and exit - -d, --daemon Use dbus signal from mmsd to trigger conversion + -d, --daemon start in daemon mode -f FILES [FILES ...], --file FILES [FILES ...] - Parse specified mms files and quit + Start in batch mode, parse specified mms files + --disable-smtp + --disable-mms-delivery --delete Ask mmsd to delete the converted MMS --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 +``` + +### Using with Mutt : +To be able to send mms with mutt you need it to be built with SMTP support. +And and the following line in your ```$HOME/.muttrc```: +``` +set smtp_url = "smtp://localhost:2525" +set ssl_starttls = no +set ssl_force_tls = no + ``` \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..0139b4f --- /dev/null +++ b/TODO.md @@ -0,0 +1,11 @@ +# mms2mail + +## To Do + +* Convert all previously received mms on start + +* HTML message + * mms2mail : add as HTML body, currently added as attachments + * mail2mms : convert to text plain in case of html only mail + +* A lot of other things... \ No newline at end of file diff --git a/mms2mail b/mms2mail index dc5e1bd..3631315 100755 --- a/mms2mail +++ b/mms2mail @@ -17,46 +17,67 @@ from gi.repository import GLib import dbus import dbus.mainloop.glib +import os +import re +import tempfile +from aiosmtpd.controller import Controller +from email import parser + log = logging.getLogger(__name__) +class Config: + """Allow sharing configuration between classes.""" + + def __init__(self): + """Return the config instance.""" + self.config = configparser.ConfigParser() + self.config.read(f"{Path.home()}/.mms/modemmanager/mms2mail.ini") + + def get_config(self): + """Return the config element. + + :rtype ConfigParser + :return The parsed configuration + """ + return self.config + + class MMS2Mail: """ The class handling the conversion between MMS and mail format. MMS support is provided by python-messaging - Mail support is provided by marrow.mailer """ - def __init__(self, delete=False, force_read=False, + def __init__(self, config, delete=False, force_read=False, force_unlock=False): """ Return class instance. + :param config: The module configuration file + :type config: ConfigParser + :param delete: delete MMS after conversion :type delete: bool :param force_read: force converting an already read MMS (batch mode) :type force_read: bool - :param disable_dbus: Disable sending dbus commands to mmsd (batch mode) - :type disable_dbus: bool - :param force_unlock: Force mbox unlocking after a few minutes :type force_unlock: bool """ self.delete = delete self.force_read = force_read self.force_unlock = force_unlock - self.config = configparser.ConfigParser() - self.config.read(f"{Path.home()}/.mms/modemmanager/mms2mail.ini") - self.attach_mms = self.config.getboolean('mail', 'attach_mms', - fallback=False) - self.domain = self.config.get('mail', 'domain', - fallback=socket.getfqdn()) - self.user = self.config.get('mail', 'user', fallback=getpass.getuser()) - mbox_file = self.config.get('mail', 'mailbox', - fallback=f"/var/mail/{self.user}") + cfg = config.get_config() + self.attach_mms = cfg.getboolean('mail', 'attach_mms', + fallback=False) + self.domain = cfg.get('mail', 'domain', + fallback=socket.getfqdn()) + self.user = cfg.get('mail', 'user', fallback=getpass.getuser()) + mbox_file = cfg.get('mail', 'mailbox', + fallback=f"/var/mail/{self.user}") self.mailbox = mailbox.mbox(mbox_file) self.dbus = None @@ -178,7 +199,8 @@ class MMS2Mail: maintype, subtype = datacontent[0].split('/', 1) if 'text/plain' in datacontent[0]: encoding = datacontent[1].get('Charset', 'utf-8') - body += data_part.data.decode(encoding) + '\n' + body += data_part.data.decode(encoding, + errors='replace') + '\n' continue extension = str(mimetypes.guess_extension(datacontent[0])) filename = datacontent[1].get('Name', str(data_id)) @@ -246,6 +268,103 @@ class MMS2Mail: self.dbus.delete_mms(dbus_path) +class Mail2MMSHandler: + """The class handling the conversion between mail and MMS format.""" + + def __init__(self, dbusmmsd): + """ + Return the Mail2MMS instance. + + :param dbusmmsd: The DBus MMSd abstraction class + :type dbusmmsd: DbusMMSd() + + :param config: The module configuration file + :type config: ConfigParser + """ + self.parser = parser.BytesParser() + self.pattern = re.compile('^\+[0-9]+$') + self.dbusmmsd = dbusmmsd + mmsd_config = dbusmmsd.get_manager_config() + self.auto_create_smil = mmsd_config.get('AutoCreateSMIL', False) + self.max_attachments = mmsd_config.get('MaxAttachments', 25) + self.total_max_attachment_size = mmsd_config.get( + 'TotalMaxAttachmentSize', + 1100000) + self.use_delivery_reports = mmsd_config.get('UseDeliveryReports', + False) + + async def handle_DATA(self, server, session, envelope): + """ + Handle the reception of a new mail via smtp. + + :param server: The SMTP server instance + :type server: SMTP + + :param session: The session instance currently being handled + :type session: Session + + :param envelope: The envelope instance of the current SMTP Transaction + :type envelope: Envelope + """ + recipients = [] + attachments = [] + smil = None + for r in envelope.rcpt_tos: + number = r.split('@')[0] + if self.pattern.search(number): + log.debug(f'Add recipient number : {number}') + recipients.append(number) + else: + log.debug(f'Ignoring recipient : {r}') + if len(recipients) == 0: + log.info('No sms recipient') + return '553 Requested action not taken: mailbox name not allowed' + + mail = self.parser.parsebytes(envelope.content) + cid = 1 + total_size = 0 + with tempfile.TemporaryDirectory(prefix='mailtomms-') as tmp_dir: + for part in mail.walk(): + content_type = part.get_content_type() + if 'multipart' in content_type: + continue + filename = part.get_filename() + if not filename: + ext = mimetypes.guess_extension(part.get_content_type()) + if not ext: + # Use a generic bag-of-bits extension + ext = '.bin' + filename = f'part-{cid:03d}{ext}' + if filename == 'smil.xml': + smil = part.get_payload(decode=True) + continue + path = os.path.join(tmp_dir, filename) + if content_type == 'text/plain': + with open(path, 'wt', encoding='utf-8') as af: + charset = part.get_content_charset(failobj='utf-8') + total_size += af.write(part. + get_payload(decode=True). + decode(charset)) + else: + with open(path, 'wb') as af: + total_size += af.write(part. + get_payload(decode=True)) + attachments.append((f"cid-{cid}", content_type, path)) + cid += 1 + if len(attachments) == 0: + return '550 No attachments found' + elif len(attachments) > self.max_attachments: + return '550 Too much attachments' + elif total_size > self.total_max_attachment_size: + return '554 5.3.4 Message too big for system' + try: + self.dbusmmsd.send_mms(recipients, attachments, smil) + except dbus.exceptions.DBusException as e: + log.error(e) + return '421 mmsd service not available' + return '250 OK' + + class DbusMMSd(): """Use DBus communication with mmsd.""" @@ -297,6 +416,69 @@ class DbusMMSd(): log.debug(f"Deleting MMS {dbus_path}") message.Delete() + def get_service(self): + """ + Get mmsd Service Interface. + + :return the mmsd service + :rtype dbus.Interface + """ + manager = dbus.Interface(self.bus.get_object('org.ofono.mms', + '/org/ofono/mms'), + 'org.ofono.mms.Manager') + services = manager.GetServices() + path = services[0][0] + service = dbus.Interface(self.bus.get_object('org.ofono.mms', path), + 'org.ofono.mms.Service') + return service + + def get_manager_config(self): + """ + Ask mmsd its properties. + + :return the mmsd manager service properties + :rtype dict + """ + service = self.get_service() + return service.GetProperties() + + def send_mms(self, recipients, attachments, smil=None): + """ + Ask mmsd to send a MMS. + + :param recipients: The mms recipients phone numbers + :type recipients: Array(str) + + :param attachments: The mms attachments [name, mime type, filepath] + :type attachments: Array(str,str,str) + + :param smil: The Smil.xml content allowing MMS customization + :type smil: str + """ + service = self.get_service() + + mms_recipients = dbus.Array([], signature=dbus.Signature('s')) + for r in recipients: + mms_recipients.append(dbus.String(r)) + + if smil: + log.debug("Send MMS as Related") + mms_smil = dbus.String(smil) + else: + log.debug("Send MMS as Mixed") + mms_smil = "" + + mms_attachments = dbus.Array([], signature=dbus.Signature('(sss)')) + for a in attachments: + log.debug("Attachment: ({})".format(a)) + mms_attachments.append(dbus.Struct((dbus.String(a[0]), + dbus.String(a[1]), + dbus.String(a[2]) + ), signature=None)) + + path = service.SendMessage(mms_recipients, mms_smil, mms_attachments) + log.debug(path) + def add_signal_receiver(self): """Add a signal receiver to the current bus.""" if self.mms2mail: @@ -326,10 +508,15 @@ def main(): parser = argparse.ArgumentParser() mode = parser.add_mutually_exclusive_group() mode.add_argument("-d", "--daemon", - help="Use dbus signal from mmsd to trigger conversion", - action='store_true', dest='watcher') + help="start in daemon mode ", + action='store_true', dest='daemon') mode.add_argument("-f", "--file", nargs='+', - help="Parse specified mms files and quit", dest='files') + help="Start in batch mode, parse specified mms files", + dest='files') + parser.add_argument('--disable-smtp', action='store_true', + dest='disable_smtp') + parser.add_argument('--disable-mms-delivery', action='store_true', + dest='disable_mms_delivery') parser.add_argument('--delete', action='store_true', dest='delete', help="Ask mmsd to delete the converted MMS") parser.add_argument('--force-read', action='store_true', @@ -356,8 +543,20 @@ def main(): ch.setFormatter(formatter) log.addHandler(ch) + c = Config() d = DbusMMSd() - m = MMS2Mail(delete=args.delete, force_read=args.force_read, + + h = Mail2MMSHandler(dbusmmsd=d) + + controller = Controller(h, + hostname=c.get_config().get('smtp', 'hostname', + fallback='localhost'), + port=c.get_config().get('smtp', 'port', + fallback=2525)) + + m = MMS2Mail(config=c, + delete=args.delete, + force_read=args.force_read, force_unlock=args.force_unlock) m.set_dbus(d) @@ -365,13 +564,21 @@ def main(): for mms_file in args.files: m.convert(path=mms_file) return - elif args.watcher: + elif args.daemon: log.info("Starting mms2mail in daemon mode") - d.set_mms2mail(m) - d.add_signal_receiver() + if not args.disable_smtp: + log.info("Activating smtp to mmsd server") + controller.start() + if not args.disable_mms_delivery: + log.info("Activating mms to mbox server") + d.set_mms2mail(m) + d.add_signal_receiver() else: parser.print_help() + return + d.run() + controller.stop() if __name__ == '__main__': diff --git a/mms2mail.ini b/mms2mail.ini index 4ec222e..5f0f75a 100644 --- a/mms2mail.ini +++ b/mms2mail.ini @@ -3,3 +3,7 @@ mailbox = /var/mail/mobian account = mobian domain = mobian.lan attach_mms = false + +[smtp] +hostname = localhost +port = 2525 -- 2.40.1 From 127e5cffe33e6b49d7942441cd4d2ce74599caf9 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 19 May 2021 06:42:31 +0200 Subject: [PATCH 07/12] [feat] Out mms subject support (mmsd-tng) --- README.md | 2 ++ mms2mail | 105 ++++++++++++++++++++++++++++++------------------------ 2 files changed, 61 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 0240682..ffa54d7 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,14 @@ By default: - python3 - python3-aiosmtpd + - python3-pydbus - python-messaging (pip install python-messaging) ### setup Install the dependency and mms2mail (on debian based distribution): ``` sudo apt-get install python3 +sudo apt-get install python3-pydbus sudo apt-get install python3-aiosmtpd pip install --user python-messaging diff --git a/mms2mail b/mms2mail index 3631315..f82d0f0 100755 --- a/mms2mail +++ b/mms2mail @@ -14,8 +14,7 @@ import mailbox import email from gi.repository import GLib -import dbus -import dbus.mainloop.glib +import pydbus import os import re @@ -115,7 +114,7 @@ class MMS2Mail: return None return status['info']['state'] - def message_added(self, name, value, member, path, interface): + def message_added(self, name, value): """Trigger conversion on MessageAdded signal.""" if value['Status'] == 'downloaded' or value['Status'] == 'received': log.debug(f"New incoming MMS found ({name.split('/')[-1]})") @@ -309,6 +308,7 @@ class Mail2MMSHandler: recipients = [] attachments = [] smil = None + for r in envelope.rcpt_tos: number = r.split('@')[0] if self.pattern.search(number): @@ -321,6 +321,7 @@ class Mail2MMSHandler: return '553 Requested action not taken: mailbox name not allowed' mail = self.parser.parsebytes(envelope.content) + subject = mail.get('subject', failobj=None) cid = 1 total_size = 0 with tempfile.TemporaryDirectory(prefix='mailtomms-') as tmp_dir: @@ -358,8 +359,11 @@ class Mail2MMSHandler: elif total_size > self.total_max_attachment_size: return '554 5.3.4 Message too big for system' try: - self.dbusmmsd.send_mms(recipients, attachments, smil) - except dbus.exceptions.DBusException as e: + self.dbusmmsd.send_mms(recipients=recipients, + attachments=attachments, + subject=subject, + smil=smil) + except Exception as e: log.error(e) return '421 mmsd service not available' return '250 OK' @@ -376,8 +380,7 @@ class DbusMMSd(): :type mms2mail: mms2mail() """ self.mms2mail = mms2mail - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - self.bus = dbus.SessionBus() + self.bus = pydbus.SessionBus() def set_mms2mail(self, mms2mail): """ @@ -395,9 +398,7 @@ class DbusMMSd(): :param dbus_path: the mms dbus path :type dbus_path: str """ - message = dbus.proxies.Interface(self.bus.get_object('org.ofono.mms', - dbus_path), - 'org.ofono.mms.Message') + message = self.bus.get('org.ofono.mms', dbus_path) log.debug(f"Marking MMS as read {dbus_path}") message.MarkRead() @@ -408,11 +409,7 @@ class DbusMMSd(): :param dbus_path: the mms dbus path :type dbus_path: str """ - if self.disable_dbus: - return None - message = dbus.proxies.Interface(self.bus.get_object('org.ofono.mms', - dbus_path), - 'org.ofono.mms.Message') + message = self.bus.get('org.ofono.mms', dbus_path) log.debug(f"Deleting MMS {dbus_path}") message.Delete() @@ -423,13 +420,10 @@ class DbusMMSd(): :return the mmsd service :rtype dbus.Interface """ - manager = dbus.Interface(self.bus.get_object('org.ofono.mms', - '/org/ofono/mms'), - 'org.ofono.mms.Manager') + manager = self.bus.get('org.ofono.mms', '/org/ofono/mms') services = manager.GetServices() path = services[0][0] - service = dbus.Interface(self.bus.get_object('org.ofono.mms', path), - 'org.ofono.mms.Service') + service = self.bus.get('org.ofono.mms', path) return service def get_manager_config(self): @@ -442,7 +436,28 @@ class DbusMMSd(): service = self.get_service() return service.GetProperties() - def send_mms(self, recipients, attachments, smil=None): + def get_send_message_version(self): + """ + Ask mmsd its SendMessage method Signature. + + :return true if mmsd is mmsd-tng allowing Subject in mms + :rtype bool + """ + if not hasattr(self, 'mmsdtng'): + from xml.dom import minidom + mmsdtng = False + svc = self.get_service() + i = svc.Introspect() + dom = minidom.parseString(i) + for method in dom.getElementsByTagName('method'): + if method.getAttribute('name') == "SendMessage": + for arg in method.getElementsByTagName('arg'): + if arg.getAttribute('name') == 'options': + mmsdtng = True + self.mmsdtng = mmsdtng + return self.mmsdtng + + def send_mms(self, recipients, attachments, subject=None, smil=None): """ Ask mmsd to send a MMS. @@ -457,37 +472,35 @@ class DbusMMSd(): """ service = self.get_service() - mms_recipients = dbus.Array([], signature=dbus.Signature('s')) - for r in recipients: - mms_recipients.append(dbus.String(r)) - - if smil: - log.debug("Send MMS as Related") - mms_smil = dbus.String(smil) + mmsdtng = self.get_send_message_version() + if mmsdtng: + log.debug("Using mmsd-tng as backend") + option_list = {} + if subject: + log.debug(f"MMS Subject = {subject}") + option_list['Subject'] = GLib.Variant('s', subject) + if smil: + log.debug("Send MMS as Related") + option_list['smil'] = GLib.Variant('s', smil) + options = GLib.Variant('a{sv}', option_list) + path = service.SendMessage(recipients, options, + attachments) else: - log.debug("Send MMS as Mixed") - mms_smil = "" - - mms_attachments = dbus.Array([], signature=dbus.Signature('(sss)')) - for a in attachments: - log.debug("Attachment: ({})".format(a)) - mms_attachments.append(dbus.Struct((dbus.String(a[0]), - dbus.String(a[1]), - dbus.String(a[2]) - ), signature=None)) - - path = service.SendMessage(mms_recipients, mms_smil, mms_attachments) + log.debug("Using mmsd as backend") + if smil: + log.debug("Send MMS as Related") + else: + log.debug("Send MMS as Mixed") + smil = "" + path = service.SendMessage(recipients, smil, + attachments) log.debug(path) def add_signal_receiver(self): """Add a signal receiver to the current bus.""" if self.mms2mail: - self.bus.add_signal_receiver(self.mms2mail.message_added, - bus_name="org.ofono.mms", - signal_name="MessageAdded", - member_keyword="member", - path_keyword="path", - interface_keyword="interface") + service = self.get_service() + service.onMessageAdded = self.mms2mail.message_added return True else: return False -- 2.40.1 From 1f5e7f37beca422f3e8757cb3b459803dc678dad Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 19 May 2021 08:08:09 +0200 Subject: [PATCH 08/12] [feat] Convert stored mms on init --- README.md | 6 ++++++ TODO.md | 2 -- mms2mail | 21 ++++++++++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ffa54d7..3307fc3 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ systemctl --user daemon-reload systemctl --user enable mms2mail systemctl --user start mms2mail ``` + +Depending on your distribution, you might have to add your account to the ```mail``` group to be able to lock and use the system mbox. +On Debian based distribution : +``` +sudo addgroup $(whoami) mail +``` ### config An optional configuration file can be put in the home folder : ```$HOME/.mms/modemmanager/mms2mail.ini```. The default value are : diff --git a/TODO.md b/TODO.md index 0139b4f..dae34e2 100644 --- a/TODO.md +++ b/TODO.md @@ -2,8 +2,6 @@ ## To Do -* Convert all previously received mms on start - * HTML message * mms2mail : add as HTML body, currently added as attachments * mail2mms : convert to text plain in case of html only mail diff --git a/mms2mail b/mms2mail index f82d0f0..50d99f2 100755 --- a/mms2mail +++ b/mms2mail @@ -266,6 +266,13 @@ class MMS2Mail: if self.delete: self.dbus.delete_mms(dbus_path) + def convert_stored_mms(self): + """Convert all mms from mmsd storage.""" + log.info('INIT : Converting MMs from storage') + messages = self.dbus.get_messages() + for m in messages: + self.message_added(name=m[0], value=m[1]) + class Mail2MMSHandler: """The class handling the conversion between mail and MMS format.""" @@ -426,6 +433,16 @@ class DbusMMSd(): service = self.bus.get('org.ofono.mms', path) return service + def get_messages(self): + """ + Ask mmsd all stored mms. + + :return all mms from mmsd storage + :rtype Array + """ + service = self.get_service() + return service.GetMessages() + def get_manager_config(self): """ Ask mmsd its properties. @@ -531,7 +548,8 @@ def main(): parser.add_argument('--disable-mms-delivery', action='store_true', dest='disable_mms_delivery') parser.add_argument('--delete', action='store_true', dest='delete', - help="Ask mmsd to delete the converted MMS") + help="After procession ask mmsd to delete \ + the mms from its storage") parser.add_argument('--force-read', action='store_true', dest='force_read', help="Force conversion even if MMS \ is marked as read") @@ -586,6 +604,7 @@ def main(): log.info("Activating mms to mbox server") d.set_mms2mail(m) d.add_signal_receiver() + m.convert_stored_mms() else: parser.print_help() return -- 2.40.1 From de8dcbe4d41965ba3ff93a3fb7ad6d45f737062b Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 19 May 2021 08:15:58 +0200 Subject: [PATCH 09/12] [feat] Delete MMS is now a config entry --- README.md | 1 + mms2mail | 12 +++--------- mms2mail.ini | 1 + 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3307fc3..a240f40 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ mailbox = /var/mail/$USER ; the mailbox where mms are appended user = $USER ; the user account specified as recipient domain = $HOSTNAME ; the domain part appended to phone number and user attach_mms = false ; whether to attach the full mms binary file +delete_from_mmsd = false ; delete mms from mmsd storage upon successful conversion [smtp] hostname = localhost diff --git a/mms2mail b/mms2mail index 50d99f2..6c03f6a 100755 --- a/mms2mail +++ b/mms2mail @@ -49,7 +49,7 @@ class MMS2Mail: MMS support is provided by python-messaging """ - def __init__(self, config, delete=False, force_read=False, + def __init__(self, config, force_read=False, force_unlock=False): """ Return class instance. @@ -57,21 +57,19 @@ class MMS2Mail: :param config: The module configuration file :type config: ConfigParser - :param delete: delete MMS after conversion - :type delete: bool - :param force_read: force converting an already read MMS (batch mode) :type force_read: bool :param force_unlock: Force mbox unlocking after a few minutes :type force_unlock: bool """ - self.delete = delete self.force_read = force_read self.force_unlock = force_unlock cfg = config.get_config() self.attach_mms = cfg.getboolean('mail', 'attach_mms', fallback=False) + self.delete = cfg.getboolean('mail', 'delete_from_mmsd', + fallback=False) self.domain = cfg.get('mail', 'domain', fallback=socket.getfqdn()) self.user = cfg.get('mail', 'user', fallback=getpass.getuser()) @@ -547,9 +545,6 @@ def main(): dest='disable_smtp') parser.add_argument('--disable-mms-delivery', action='store_true', dest='disable_mms_delivery') - parser.add_argument('--delete', action='store_true', dest='delete', - help="After procession ask mmsd to delete \ - the mms from its storage") parser.add_argument('--force-read', action='store_true', dest='force_read', help="Force conversion even if MMS \ is marked as read") @@ -586,7 +581,6 @@ def main(): fallback=2525)) m = MMS2Mail(config=c, - delete=args.delete, force_read=args.force_read, force_unlock=args.force_unlock) m.set_dbus(d) diff --git a/mms2mail.ini b/mms2mail.ini index 5f0f75a..fa4f363 100644 --- a/mms2mail.ini +++ b/mms2mail.ini @@ -3,6 +3,7 @@ mailbox = /var/mail/mobian account = mobian domain = mobian.lan attach_mms = false +delete_from_mmsd = false [smtp] hostname = localhost -- 2.40.1 From 213d73a4753d8df3af67cc4674ff865edbece925 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 19 May 2021 19:21:20 +0200 Subject: [PATCH 10/12] [feat] remove python-messaging --- .vscode/settings.json | 5 ++ README.md | 15 ++-- mms2mail | 160 +++++++++++++++++++----------------------- 3 files changed, 85 insertions(+), 95 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d9d7eb9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.linting.flake8Enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.enabled": true +} \ No newline at end of file diff --git a/README.md b/README.md index a240f40..e10b9af 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ By default: - python3 - python3-aiosmtpd - python3-pydbus - - python-messaging (pip install python-messaging) ### setup Install the dependency and mms2mail (on debian based distribution): @@ -26,7 +25,6 @@ Install the dependency and mms2mail (on debian based distribution): sudo apt-get install python3 sudo apt-get install python3-pydbus sudo apt-get install python3-aiosmtpd -pip install --user python-messaging mkdir -p ~/.local/bin cp mms2mail ~/.local/bin @@ -66,23 +64,24 @@ port = 2525 ### reference ``` - mms2mail [-h] [-d | -f FILES [FILES ...]] [--disable-smtp] [--disable-mms-delivery] [--delete] [--force-read] [--force-unlock] [-l {critical,error,warning,info,debug}] +mms2mail [-h] [--disable-smtp] [--disable-mms-delivery] [--force-read] [--force-unlock] [-l {critical,error,warning,info,debug}] optional arguments: -h, --help show this help message and exit - -d, --daemon start in daemon mode - -f FILES [FILES ...], --file FILES [FILES ...] - Start in batch mode, parse specified mms files --disable-smtp --disable-mms-delivery - --delete Ask mmsd to delete the converted MMS --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 /!\ -l {critical,error,warning,info,debug}, --logging {critical,error,warning,info,debug} Define the logger output level ``` -### Using with Mutt : +### Sending MMS + +To send MMS, mail address not in the following format would be ignored : +```+123456789@domain``` with phone number in international format. + +#### with Mutt : To be able to send mms with mutt you need it to be built with SMTP support. And and the following line in your ```$HOME/.muttrc```: ``` diff --git a/mms2mail b/mms2mail index 6c03f6a..cc0793c 100755 --- a/mms2mail +++ b/mms2mail @@ -9,12 +9,12 @@ import time import logging from pathlib import Path -from messaging.mms.message import MMSMessage import mailbox import email from gi.repository import GLib -import pydbus +from pydbus import SessionBus +from datetime import datetime import os import re @@ -69,7 +69,7 @@ class MMS2Mail: self.attach_mms = cfg.getboolean('mail', 'attach_mms', fallback=False) self.delete = cfg.getboolean('mail', 'delete_from_mmsd', - fallback=False) + fallback=False) self.domain = cfg.get('mail', 'domain', fallback=socket.getfqdn()) self.user = cfg.get('mail', 'user', fallback=getpass.getuser()) @@ -87,13 +87,16 @@ class MMS2Mail: """ self.dbus = dbusmmsd - def check_mms(self, path): + def check_mms(self, path, properties): """ Check wether the provided file would be converted. :param path: the mms filesystem path :type path: str + :param properties: the mms properties + :type properties: Array + :return the mms status or None :rtype str """ @@ -121,7 +124,7 @@ class MMS2Mail: else: log.debug(f"New outgoing MMS found ({name.split('/')[-1]})") - def convert(self, path, dbus_path=None, properties=None): + def convert(self, path, dbus_path, properties): """ Convert a provided mms file to a mail stored in a mbox. @@ -130,80 +133,77 @@ class MMS2Mail: :param dbus_path: the mms dbus path :type dbus_path: str + + :param properties: the mms properties + :type properties: Array """ # Check if the provided file present - status = self.check_mms(path) + status = self.check_mms(path, properties) if not status: log.error("MMS file not convertible.") return - # Generate its dbus path, for future operation (mark as read, delete) - if not dbus_path: - dbus_path = f"/org/ofono/mms/modemmanager/{path.split('/')[-1]}" - mms = MMSMessage.from_file(path) message = email.message.EmailMessage() # Generate Mail Headers - 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'] + mms_from = properties.get('Sender', "unknown") + log.debug(f"MMS[From]: {mms_from}") + if '@' in mms_from: + message['From'] = mms_from else: - if status == 'sent' or status == 'draft': - message['Subject'] = f"MMS to {mms_to}" + message['From'] = f"{mms_from}@{self.domain}" + + to = properties.get('Modem Number', None) + if to: + message['To'] = f"{mms_from}@{self.domain}" + recipients = "" + for r in properties['Recipients']: + if to and to in r: + continue + log.debug(f'MMS[CC] : {r}') + if '@' in r: + recipients += f"{r}," else: - message['Subject'] = f"MMS from {mms_from}" + recipients += f"{r}@{self.domain}," + if recipients: + recipients = recipients[:-1] + if to: + message['CC'] = recipients + else: + message['To'] = recipients - if 'Date' in mms.headers and mms.headers['Date']: - message['Date'] = mms.headers['Date'] - - # Recopy MMS HEADERS - for header in mms.headers: - message.add_header(f"X-MMS-{header}", f"{mms.headers[header]}") + message['Subject'] = properties.get('Subject', + f"MMS from {mms_from}") + mms_date = properties.get('Date') + if mms_date: + mms_datetime = datetime.strptime(mms_date, '%Y-%m-%dT%H:%M:%S%z') + mail_date = email.utils.format_datetime(mms_datetime) + message['Date'] = mail_date or email.utils.formatdate() 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') - body += data_part.data.decode(encoding, - errors='replace') + '\n' + for attachment in properties['Attachments']: + cid = attachment[0] + mimetype = attachment[1] + contentfile = attachment[2] + offset = attachment[3] + size = attachment[4] + with open(contentfile, 'rb') as f: + f.seek(offset, 0) + content = f.read(size) + if mimetype is not None: + if 'text/plain' in mimetype: + mimetype, charset = mimetype.split(';', 1) + encoding = charset.split('=')[1] + body += content.decode(encoding, + errors='replace') + '\n' continue - extension = str(mimetypes.guess_extension(datacontent[0])) - filename = datacontent[1].get('Name', str(data_id)) - attachments.append([data_part.data, maintype, + maintype, subtype = mimetype.split('/', 1) + extension = str(mimetypes.guess_extension(mimetype)) + filename = cid + attachments.append([content, maintype, subtype, filename + extension]) - data_id = data_id + 1 if body: message.set_content(body) for a in attachments: @@ -212,7 +212,8 @@ class MMS2Mail: subtype=a[2], filename=a[3]) - # Add MMS binary file, for debugging purpose or reparsing in the future + # Add MMS binary file, for debugging purpose + # or reparsing in the future if self.attach_mms: with open(path, 'rb') as fp: message.add_attachment(fp.read(), @@ -385,7 +386,7 @@ class DbusMMSd(): :type mms2mail: mms2mail() """ self.mms2mail = mms2mail - self.bus = pydbus.SessionBus() + self.bus = SessionBus() def set_mms2mail(self, mms2mail): """ @@ -534,13 +535,6 @@ class DbusMMSd(): def main(): """Run the different functions handling mms and mail.""" parser = argparse.ArgumentParser() - mode = parser.add_mutually_exclusive_group() - mode.add_argument("-d", "--daemon", - help="start in daemon mode ", - action='store_true', dest='daemon') - mode.add_argument("-f", "--file", nargs='+', - help="Start in batch mode, parse specified mms files", - dest='files') parser.add_argument('--disable-smtp', action='store_true', dest='disable_smtp') parser.add_argument('--disable-mms-delivery', action='store_true', @@ -585,23 +579,15 @@ def main(): force_unlock=args.force_unlock) m.set_dbus(d) - if args.files: - for mms_file in args.files: - m.convert(path=mms_file) - return - elif args.daemon: - log.info("Starting mms2mail in daemon mode") - if not args.disable_smtp: - log.info("Activating smtp to mmsd server") - controller.start() - if not args.disable_mms_delivery: - log.info("Activating mms to mbox server") - d.set_mms2mail(m) - d.add_signal_receiver() - m.convert_stored_mms() - else: - parser.print_help() - return + log.info("Starting mms2mail") + if not args.disable_smtp: + log.info("Activating smtp to mmsd server") + controller.start() + if not args.disable_mms_delivery: + log.info("Activating mms to mbox server") + d.set_mms2mail(m) + d.add_signal_receiver() + m.convert_stored_mms() d.run() controller.stop() -- 2.40.1 From dd36091c3f902a9c8992302362954e11c1b406cd Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 21 May 2021 07:51:02 +0200 Subject: [PATCH 11/12] [feat] Add Makefile to automate installation --- Makefile | 18 ++++++++++++++++++ README.md | 25 ++++++++++++++++++++++++- mms2mail.service | 2 +- requirements.txt | 2 ++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 requirements.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ff7038b --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +install: + mkdir -p ${HOME}/.local/bin + mkdir -p ${HOME}/.config/systemd/user + install -m 700 ./mms2mail ${HOME}/.local/bin/ + install -m 755 ./mms2mail.service ${HOME}/.config/systemd/user/ + systemctl --user daemon-reload + +configure: + systemctl --user enable mms2mail + +start: + systemctl --user start mms2mail + +deb-deps: + sudo apt install python3-pydbus python3-aiosmtpd + +pypy-deps: + pip install --user -r requirements.txt diff --git a/README.md b/README.md index e10b9af..ae8b3a1 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,29 @@ By default: - python3-pydbus ### setup + +Install the dependency and mms2mail (on debian based distribution): +``` +make deb-deps install +``` +For other distribution: +``` +make pypy-deps install +``` + +To enable the daemon mode in systemd user : +``` +make configure start +``` + +Depending on your distribution, you might have to add your account to the ```mail``` group to be able to lock and use the system mbox. +On Debian based distribution : +``` +sudo addgroup $(whoami) mail +``` + +#### manual install + Install the dependency and mms2mail (on debian based distribution): ``` sudo apt-get install python3 @@ -89,4 +112,4 @@ set smtp_url = "smtp://localhost:2525" set ssl_starttls = no set ssl_force_tls = no -``` \ No newline at end of file +``` diff --git a/mms2mail.service b/mms2mail.service index 4f24954..5982731 100644 --- a/mms2mail.service +++ b/mms2mail.service @@ -3,7 +3,7 @@ Description=Multimedia Messaging Service to Mail converter Daemon After=mmsd.service [Service] -ExecStart=python3 %h/.local/bin/mms2mail -d +ExecStart=python3 %h/.local/bin/mms2mail Restart=on-failure RestartSec=10s diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..30c9e7a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pydbus +aiosmtpd -- 2.40.1 From badce4c5fcab55296f5e5b50804bc886bee6b576 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 21 May 2021 07:54:16 +0200 Subject: [PATCH 12/12] [fix] Gitignore --- .gitignore | 2 ++ .vscode/settings.json | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 13d1490..2f27ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,5 @@ dmypy.json # Pyre type checker .pyre/ +#VScode +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d9d7eb9..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "python.linting.flake8Enabled": true, - "python.linting.pylintEnabled": false, - "python.linting.enabled": true -} \ No newline at end of file -- 2.40.1