From de6875ecf9d6881a820fcdf31c669d847a3c2c98 Mon Sep 17 00:00:00 2001 From: "Bjarni R. Einarsson" Date: Thu, 6 Aug 2015 19:51:40 +0100 Subject: [PATCH 1/6] Make thread reaper more tolerant; see issue #1367 --- mailpile/crypto/gpgi.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mailpile/crypto/gpgi.py b/mailpile/crypto/gpgi.py index 40e4a3111..8f208ce90 100644 --- a/mailpile/crypto/gpgi.py +++ b/mailpile/crypto/gpgi.py @@ -589,11 +589,12 @@ def run(self, return gpg_retcode, self.outputbuffers def _reap_threads(self): - for name, thr in self.threads.iteritems(): - if thr.isAlive(): - thr.join(timeout=15) + for tries in (1, 2, 3): + for name, thr in self.threads.iteritems(): if thr.isAlive(): - print 'SCARY WARNING: FAILED TO REAP THREAD %s' % thr + thr.join(timeout=15) + if thr.isAlive() and tries > 1: + print 'WARNING: Failed to reap thread %s' % thr def parse_status(self, line, *args): self.debug('< Date: Fri, 7 Aug 2015 18:45:58 +0100 Subject: [PATCH 2/6] Disable autotagging unless e-mails are incoming, fixes #1346 --- mailpile/plugins/autotag.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mailpile/plugins/autotag.py b/mailpile/plugins/autotag.py index 6367f5f8e..dc20ea7bd 100644 --- a/mailpile/plugins/autotag.py +++ b/mailpile/plugins/autotag.py @@ -366,8 +366,11 @@ def command(self): ##[ Keywords ]################################################################ -def filter_hook(session, msg_mid, msg, keywords, **ignored_kwargs): +def filter_hook(session, msg_mid, msg, keywords, **kwargs): """Classify this message.""" + if not kwargs.get('incoming', False): + return keywords + config = session.config for at_config in config.prefs.autotag: try: From ab674ff619b9051b3334d2696d9c037fd5eda604 Mon Sep 17 00:00:00 2001 From: "Bjarni R. Einarsson" Date: Fri, 7 Aug 2015 23:59:00 +0100 Subject: [PATCH 3/6] Delete old mem stats tracking file --- MEM-STATS.TXT | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 MEM-STATS.TXT diff --git a/MEM-STATS.TXT b/MEM-STATS.TXT deleted file mode 100644 index f36f3412f..000000000 --- a/MEM-STATS.TXT +++ /dev/null @@ -1,22 +0,0 @@ -This is a rough log of how much RAM Mailpile is using -in various configurations. Just so we can keep track -of things! - -Please measure by checking the RES column of top right -after restarting the app and divide by the number of -messages indexed. - - Date Messages Bytes/MSG Who? ----------- -------- --------- ----- -2013-11-20 73149 1433 bre -2013-11-22 109181 1267 bre -2013-12-11 5981 987 ng -2014-12-19 132841 1459 ani -2013-12-26 91981 1378 ng -2013-12-27 218362 2534 kyle -2014-01-02 183962 2237 ani -2014-01-07 148783 1498 ng -2014-01-10 201843 2612 ani -2014-01-26 117807 1655 bre -2014-01-28 16830 5047 laz -2014-04-26 74754 2118 bre From 5a1f743005328b80ce8c77dbff5d0950dd6f46d3 Mon Sep 17 00:00:00 2001 From: "Bjarni R. Einarsson" Date: Sat, 8 Aug 2015 00:05:02 +0100 Subject: [PATCH 4/6] Compensate for Majordomo breaking base64 text/plain parts, fixes #1279 --- mailpile/mailutils.py | 17 ++++++- mailpile/search.py | 4 +- mailpile/tests/data/tests.mbx | 92 +++++++++++++++++++++++++++++++++-- scripts/mailpile-test.py | 5 +- 4 files changed, 108 insertions(+), 10 deletions(-) diff --git a/mailpile/mailutils.py b/mailpile/mailutils.py index 611b860ac..b09209bba 100644 --- a/mailpile/mailutils.py +++ b/mailpile/mailutils.py @@ -143,6 +143,20 @@ def MakeGnuPG(*args, **kwargs): return message +def GetTextPayload(part): + mimetype = part.get_content_type() or 'text/plain' + cte = part.get('content-transfer-encoding', '').lower() + if mimetype[:5] == 'text/' and cte == 'base64': + # Mailing lists like to mess with text/plain parts, and Majordomo + # in particular isn't aware of base64 encoding. Compensate! + payload = part.get_payload(None, False) or '' + parts = payload.split('\n--') + parts[0] = base64.b64decode(parts[0]) + return '\n--'.join(parts) + else: + return part.get_payload(None, True) or '' + + def ExtractEmails(string, strip_keys=True): emails = [] startcrap = re.compile('^[\'\"<(]') @@ -1232,8 +1246,7 @@ def decode_text(self, payload, charset='utf-8', binary=True): def decode_payload(self, part): charset = part.get_content_charset() or None - payload = part.get_payload(None, True) or '' - return self.decode_text(payload, charset=charset) + return self.decode_text(GetTextPayload(part), charset=charset) def parse_text_part(self, data, charset, crypto): psi = crypto['signature'] diff --git a/mailpile/search.py b/mailpile/search.py index cf03a8dec..fb20771d2 100644 --- a/mailpile/search.py +++ b/mailpile/search.py @@ -16,7 +16,7 @@ from mailpile.i18n import ngettext as _n from mailpile.plugins import PluginManager from mailpile.mailutils import FormatMbxId, MBX_ID_LEN, NoSuchMailboxError -from mailpile.mailutils import AddressHeaderParser +from mailpile.mailutils import AddressHeaderParser, GetTextPayload from mailpile.mailutils import ExtractEmails, ExtractEmailAndName from mailpile.mailutils import Email, ParseMessage, HeaderPrint from mailpile.postinglist import GlobalPostingList @@ -1268,7 +1268,7 @@ def read_message(self, session, def _loader(p): if payload[0] is None: - payload[0] = self.try_decode(p.get_payload(None, True), + payload[0] = self.try_decode(GetTextPayload(p), charset) return payload[0] diff --git a/mailpile/tests/data/tests.mbx b/mailpile/tests/data/tests.mbx index be62333b5..fab6eb161 100644 --- a/mailpile/tests/data/tests.mbx +++ b/mailpile/tests/data/tests.mbx @@ -2729,8 +2729,8 @@ YIC3qNe8fqErjT5dm/UJHBRd8H8hMREbBhlGGw== Yay, that is so neat and cool and funky and awesome. -From barnaby@waterpigs.co.uk Fri Jan 17 17:28:01 2014 -Return-Path: +From barnaby@woots Fri Jan 17 17:28:01 2014 +Return-Path: X-Original-To: team+testing@mailpile.is Delivered-To: mailpile@mailpile.is Received: by mailpile.is (Postfix) @@ -2751,12 +2751,12 @@ Content-Type: multipart/encrypted; boundary="Apple-Mail=_FEEA9815-A6CE-42D3-B415 Subject: PGP Testing Email Mime-Version: 1.0 (Mac OS X Mail 7.1 \(1827\)) X-Pgp-Agent: GPGMail (null) -From: Barnaby Walters +From: Barnaby Walters Date: Fri, 17 Jan 2014 17:19:50 +0000 References: <201309160027.r8G0R3Ag013323@example.com>, Content-Transfer-Encoding: 7bit -Message-Id: <52906F15-18A6-456A-A442-97667D27E4C0@waterpigs.co.uk> +Message-Id: <52906F15-18A6-456A-A442-97667D27E4C0@woots> Content-Description: OpenPGP encrypted message To: team+testing@mailpile.is X-Mailer: Apple Mail (2.1827) @@ -2812,3 +2812,87 @@ gGcS1XaPCA== --Apple-Mail=_FEEA9815-A6CE-42D3-B415-492B16ADEBAD-- +From bjarni@woots Fri Jan 17 17:28:01 2014 +Return-Path: +From: ATA NOO +To: People , + "linux-usb@vger" +CC: fx +Subject: RE: [PATCH v3 00/11] usbip: features to USB over WebSocket +Thread-Topic: [PATCH v3 00/11] usbip: features to USB over WebSocket +Thread-Index: AQHQgI4ttWgT3k26+ZNRhh3DyZ1iCKMAgAATgBA= +Date: Tue, 28 Apr 2015 09:35:25 +0000 +Message-ID: +References: <14300967-8006-1-git-send-email-nobuo.iwata@fujihamfist> +In-Reply-To: <553F3@sumsing> +Accept-Language: ja-JP, en-US +Content-Language: ja-JP +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: base64 +MIME-Version: 1.0 +X-OriginatorOrg: fujihamfist +Sender: linux-usb-owner@vger +Precedence: bulk +List-ID: +X-Mailing-List: linux-usb@vger + +SGVsbG8sDQoNCj4gQXMgZmFyIGFzIEkgdW5kZXJzdGFuZCB5b3VyIGRlc2lnbiB5b3UgaGF2ZSBr +ZXJuZWwgc3R1YiBkcml2ZXIgd2hpY2ggDQo+IGlzIHNlbmRpbmcgYW5kIHJlY2VpdmluZyBkYXRh +IHZpYSBzb2NrZXQgZmQgcmVjZWl2ZWQgZnJvbSB1c2Vyc3BhY2UuDQo+IE5vdyBhZnRlciB0aGlz +IHNlcmllcyB5b3UgYXJlIGV4cG9ydGluZyBhbGwgbWVzc2FnZXMgIHRvIHVzZXJzcGFjZQ0KPiB3 +aGVyZSBkYWVtb24gaXMgc2VuZGluZyB0aGVtIHVzaW5nIHdlYiBzb2NrZXRzLiBBbSBJIHJpZ2h0 +Pw0KDQpZZXMuDQoNCj4gSSBkb24ndCBzZWUgd2hhdCBhcmUgdGhlIGJlbmVmaXRzIG9mIHN1Y2gg +a2VybmVsIGRyaXZlcj8NCj4gQ291bGRuJ3QgeW91IGp1c3Qgc2ltcGx5IHVzZSBsaWJ1c2IgaW4g +eW91ciBkYWVtb24gYW5kIGRvIGV2ZXJ5dGhpbmcNCj4gZnJvbSB1c2Vyc3BhY2U/IFN1Y2ggc29s +dXRpb24gY291bGQgYmUgYmVuZWZpY2lhbCBmb3Igb2xkZXIga2VybmVsDQo+IGJlY2F1c2UgeW91 +IGRvbid0IG5lZWQgdG8gYmFja3BvcnQgeW91ciBwYXRjaGVzIGJ1dCBzaW1wbHkgdXNlIHlvdXIN +Cj4gZGFlbW9uIHdoaWNoIHdpbGwgYmUgY29tcGF0aWJsZSB3aXRoIG1vc3Qga2VybmVsIHZlcnNp +b25zIGFzIGxpYnVzYiBpcw0KPiB3b3JraW5nIHdpdGggdGhlbS4NCg0KU29ycnkgcmVwZWF0aW5n +IGNvbW1lbnQgaW4gVjEgdGhyZWFkLg0KSSBoYWQgdG8gd3JpdGUgdGhpcyB0byBjaGFuZ2UgbG9n +Lg0KDQpUaGVyZSBhcmUgMiByZWFzb25zLg0KDQoxKSBBcHBsaWNhdGlvbih2aGNpX2hjZCkgc2lk +ZSBpcyBhbHNvIG5lZWRlZA0KDQpJbiBteSB1bmRlcnN0YW5kaW5nLCB1c2JmcyBwcm92aWRlcyBm +dW5jdGlvbnMgdG8gY29udHJvbCBVU0IgZGV2aWNlcy4gDQpTbyBkZXZpY2UodXNiaXBfaG9zdCkg +c2lkZSBjYW4gYmUgZG9uZSBieSB1c2JmcyBidXQgDQphcHBsaWNhdGlvbih2aGNpX2hjZCkgc2lk +ZSBjYW5ub3QuIE15IHBhdGNoIGNvdmVycyBib3RoIA0KZGV2aWNlKHVzYmlwX2hvc3QpIGFuZCBh +cHBsaWNhdGlvbih2aGNpX2hjZCkgc2lkZS4gDQpBbmQgaXQgaXMgcXVpdGUgdGhlIHNhbWUgaW4g +Ym90aCBzaWRlIGFuZCB3b3JrcyBzeW1tZXRyaWNhbGx5Lg0KDQoyKSBNYWludGFpbmFiaWxpdHkN +Cg0KVXNiZnMgcHJvdmlkZXMgc2ltaWxhciBpbnRlcmZhY2VzIHdoaWNoIFVTQiBjb3JlIHByb3Zp +ZGVzIHRvIFVTQiBob3N0IA0KZHJpdmVzLiBUbyB1c2UgdGhlIGludGVyZmFjZXMgaW4gdXNlciBz +cGFjZSwgaW1wbGVtZW50YXRpb24gd2hpY2ggYXJlIA0KaW5jbHVkZWQgaW4gc29tZSBwb3J0aW9u +cyBvZiB1c2JpcF9ob3N0LmtvIGFuZCB1c2JpcF9jb3JlIHNob3VsZCBiZSANCmNvcGllZCB0byB1 +c2Vyc3BhY2UuIEF0IHRoZSBzYW1lIHRpbWUsIHV0aWxpdGllcyBtdXN0IGJlIG1vZGlmaWVkIA0K +YmVjYXVzZSBpbnRlcmZhY2VzIHVzZWQgYmV0d2VlbiB0aGUgdXRpbGl0aWVzIGFuZCB0aGUga2Vy +bmVsIG1vZHVsZXMgDQooc3lzZnMpIHdpbGwgYmUgY2hhbmdlZCB0byBmdW5jdGlvbiBjYWxscyBp +biB1c2Vyc3BhY2UuDQoNCkZvciBleGFtcGxlLCBJJ2QgbGlrZSB0byBicmVhayBkb3duIHVzYmlw +X2hvc3Qua28gYXMgYmVsb3cuDQooYSkgc3VibWl0cyBhbmQgY2FuY2VscyBVUkJzIHRvIFVTQiBj +b3JlDQooYikgY29yZSBwYXJ0OiByZWNlaXZlcyBhbmQgaGFuZGxlIFVSQnMsIG1hbmFnZSBzdWJt +aXR0ZWQgVVJCcywgZXRjLg0KKGMpIHByb3ZpZGVzIGZ1bmN0aW9ucyB0byB1dGlsaXRpZXMgdmlh +IHN5c2ZzDQooZCkgY2FsbHMgdXNiaXBfY29tbW9uJ3MgZnVuY3Rpb25zDQooZSkgY2FsbHMga2Vy +bmVsIGZ1bmN0aW9ucw0KDQpUbyBtb3ZlIGl0IHRvIHVzZXIgc3BhY2UsDQooYSkgcmVwbGFjZSB3 +aXRoIHVzYmZzIGNhbGxzDQooYikgY29weSB0aGUgY29yZSBwYXJ0DQooYykgbW9kaWZ5IHRvIGlu +dGVyZmFjZSBpbnNpZGUgdXNlcnNwYWNlIG9yIHVzZSB1c2JmcyBkaXJlY3RseQ0KKGQpIHBvcnQg +c29tZSBwb3J0aW9uIG9mIHVzYmlwX2NvcmUNCihlKSByZXBsYWNlIHdpdGggc3lzdGVtY2FsbHMg +YW5kIGxpYnJhcmllcy4NCg0KVGhlbiwgdG8gdXNlIHVzYmZzIChhKSwgYW5vdGhlciB1c2JpcF9o +b3N0IGxpa2UgcHJvZ3JhbSB3aGljaCBoYXMgc2FtZSANCmZvciAoYikgYW5kIGRpZmZlcmVudCBp +biAoYyksIChkKSBhbmQgKGUpLiBCeSAoYyksIHV0aWxpdGllcyBzaG91bGQgYmUgDQpjaGFuZ2Vk +IHVubGVzcyBzeXNmcyBlbXVsYXRpb24gaXMgbm90IHByb3ZpZGVkLg0KDQpJIHRoaW5rIGl0J3Mg +YmV0dGVyIHRvIHVzZSB0aGUga2VybmVsIG1vZHVsZXMgYXMtaXMuIFN0cmljdGx5LCBpdCdzIA0K +YWxtb3N0IGFzLWlzIGJlY2F1c2UgSSBwdXQgYSBzbWFsbCBjb2RlIHRvIG1ha2UgcmVwbGFjZWFi +bGUgDQprZXJuZWxfc2VuZG1zZygpIGFuZCBrZXJuZWxfcmVjdm1zZygpLg0KDQpBcyBhIHJlZmVy +ZW5jZSwgSSBzdG9yZWQgbXkgcHJvdG90eXBlIGluY2x1ZGluZyB1c2Vyc3BhY2UgdXNiaXBfaG9z +dCANCndpdGggbGlidXNiKG5vdCBzeXNmcyBidXQgYSBwb3J0YWJsZSB3cmFwcGVyIG9mIHN5c2Zz +KSBpbiBzdGFnaW5nL3VzYmlwIA0Kb2YgbGludXggMy4xNC4yLiBJdCBzdGlsbCBuZWVkcyByZWZh +Y3RvcmluZy4NCmh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9kcml2ZS9mb2xkZXJzLzBCeG51V0JX +X3RCOU5mbEZEWTFobGNWQlJORWQ0WnpCMg0KVkZKM09USTBSRUZHZWxOQlYyeFhUSFJOYzBsUmVH +SmxhVFJHZERRLzBCeG51V0JXX3RCOU5mamhhYnpWTVNuWjZWR3hyVFhCDQp3TkVFMWRGSllkRU52 +YUMxSU1VZzFaRzFrVFU5aU9VTjFPRXBHVWxVDQoNCkluIHRoZSBwcm90b3R5cGUsIGxpYnNyYy9z +dHViX21haW4uYywgc3R1Yl9kZXYuYywgc3R1Yl9yeC5jIGFuZCANCnN0dWJfdHguYyBhcmUgcG9y +dGluZ3Mgb2YgdXNiaXBfaG9zdC4gc3R1Yl9jb21tb24uYyBhbmQgc3R1Yl9ldmVudC5jIGlzIA0K +dXNiaXBfY29yZS4gTWFjcm8gVVNFX0xJQlVTQiBpbiB1dGlsaXRpZXMgZGVub3RlcyBwb3J0aW9u +cyB0byBiZSANCm1vZGlmaWVkIGluIHV0aWxpdGllcy4NCg0KTXkgcGF0Y2ggd29ya3MgaW4gYm90 +aCBob3N0IGFuZCB2aGNpIHNpZGUgdXNpbmcgZXhpc3Rpbmcga2VybmVsIG1vZHVsZXMuDQoNClRo +YW5rIHlvdSBmb3IgeW91ciBjb21tZW50LA0KDQpuLml3YXRhDQovLw0K +-- +To unsubscribe from this list: send the line "unsubscribe linux-usb" in +the body of a message to majordomo@vger +More majordomo info at http://vger/majordomo-info.html diff --git a/scripts/mailpile-test.py b/scripts/mailpile-test.py index 140e68842..3bcc35853 100755 --- a/scripts/mailpile-test.py +++ b/scripts/mailpile-test.py @@ -165,7 +165,7 @@ def test_load_save_rescan(): ['from:bjarni', 'subject:inline', 'subject:encryption', 'grand', 'tag:mp_enc-mixed-decrypted'], ['from:bjarni', 'subject:signatures', '-is:unread', - 'tag:mp_sig-unverified'], + 'tag:mp_sig-expired'], ['from:brennan', 'subject:encrypted', 'testing', 'purposes', 'only', 'tag:mp_enc-decrypted'], ['from:brennan', 'subject:signed', @@ -175,6 +175,7 @@ def test_load_save_rescan(): ['from:square', 'subject:here', '-has:attachment'], [u'subject:' + IS_CHARS, 'subject:8859'], [u'subject:' + IS_CHARS, 'subject:UTF'], + ['use_libusb', 'unsubscribe', 'vger'], ): say('Searching for: %s' % search) results = mp.search(*search) @@ -182,7 +183,7 @@ def test_load_save_rescan(): say('Checking size of inbox') mp.order('flat-date') - assert(mp.search('tag:inbox').result['stats']['count'] == 18) + assert(mp.search('tag:inbox').result['stats']['count'] == 19) say('FIXME: Make sure message signatures verified') From 6d2a2360c89a23f348c3198f28b194c46f46dd33 Mon Sep 17 00:00:00 2001 From: "Bjarni R. Einarsson" Date: Sat, 8 Aug 2015 10:35:27 +0100 Subject: [PATCH 5/6] Stop converting routes to/from strings internally, fixes #700 --- mailpile/config.py | 3 +- mailpile/plugins/compose.py | 3 +- mailpile/smtp_client.py | 82 ++++++++++++++++--------------------- 3 files changed, 39 insertions(+), 49 deletions(-) diff --git a/mailpile/config.py b/mailpile/config.py index da85f18f7..4e54a0d27 100644 --- a/mailpile/config.py +++ b/mailpile/config.py @@ -2036,7 +2036,7 @@ def get_profile(self, email=None): return default_profile - def get_sendmail(self, frm, rcpts=['-t']): + def get_route(self, frm, rcpts=['-t']): if len(rcpts) == 1: if rcpts[0].lower().endswith('.onion'): return {"protocol": "smtorp", @@ -2048,7 +2048,6 @@ def get_sendmail(self, frm, rcpts=['-t']): if self.routes[routeid] is not None: return self.routes[routeid] else: - print "Migration notice: Try running 'setup/migrate'." raise ValueError(_("Route %s for %s does not exist." ) % (routeid, frm)) diff --git a/mailpile/plugins/compose.py b/mailpile/plugins/compose.py index 943b6614b..8c8be4f02 100644 --- a/mailpile/plugins/compose.py +++ b/mailpile/plugins/compose.py @@ -870,7 +870,8 @@ def command(self, emails=None): data={'mid': msg_mid, 'sid': msg_sid})) SendMail(session, msg_mid, - [PrepareMessage(config, email.get_msg(pgpmime=False), + [PrepareMessage(config, + email.get_msg(pgpmime=False), sender=sender, rcpts=(bounce_to or None), bounce=(True if bounce_to else False), diff --git a/mailpile/smtp_client.py b/mailpile/smtp_client.py index 89ae6313e..5202c7e96 100644 --- a/mailpile/smtp_client.py +++ b/mailpile/smtp_client.py @@ -95,7 +95,8 @@ def __init__(self, msg, details=None): def _RouteTuples(session, from_to_msg_ev_tuples, test_route=None): tuples = [] for frm, to, msg, events in from_to_msg_ev_tuples: - dest = {} + rcpts = {} + routes = {} for recipient in to: # If any of the events thinks this message has been delivered, # then don't try to send it again. @@ -110,27 +111,21 @@ def _RouteTuples(session, from_to_msg_ev_tuples, test_route=None): "password": "", "command": "", "host": "", - "port": 25 - } + "port": 25} if test_route: route.update(test_route) else: - route.update(session.config.get_sendmail(frm, [recipient])) - - if route["command"]: - txtroute = "|%(command)s" % route - else: - # FIXME: This is dumb, makes it hard to handle usernames - # or passwords with funky characters in them :-( - txtroute = "%(protocol)s://%(username)s:%(password)s@" \ - + "%(host)s:%(port)d" - txtroute %= route - - dest[txtroute] = dest.get(txtroute, []) - dest[txtroute].append(recipient) - for route in dest: - tuples.append((frm, route, dest[route], msg, events)) + route.update(session.config.get_route(frm, [recipient])) + + # Group together recipients that use the same route + rid = '/'.join(sorted(['%s' % (k, ) + for k in route.iteritems()])) + routes[rid] = route + rcpts[rid] = rcpts.get(rid, []) + rcpts[rid].append(recipient) + for rid in rcpts: + tuples.append((frm, routes[rid], rcpts[rid], msg, events)) return tuples @@ -147,12 +142,12 @@ def SendMail(session, msg_mid, from_to_msg_ev_tuples, # Update initial event state before we go through and start # trying to deliver stuff. - for frm, sendmail, to, msg, events in routes: + for frm, route, to, msg, events in routes: for ev in (events or []): for rcpt in to: ev.private_data['>'.join([frm, rcpt])] = False - for frm, sendmail, to, msg, events in routes: + for frm, route, to, msg, events in routes: for ev in events: ev.data['recipients'] = len(ev.private_data.keys()) ev.data['delivered'] = len([k for k in ev.private_data @@ -179,22 +174,25 @@ def smtp_do_or_die(msg, events, method, *args, **kwargs): details={'smtp_error': '%s: %s' % (rc, msg)}) # Do the actual delivering... - for frm, sendmail, to, msg, events in routes: + for frm, route, to, msg, events in routes: + route_description = route['command'] or route['host'] + frm_vcard = session.config.vcards.get_vcard(frm) update_to_vcards = msg and msg["x-mp-internal-pubkeys-attached"] if 'sendmail' in session.config.sys.debug: - sys.stderr.write(_('SendMail: from %s (%s), to %s via %s\n' - ) % (frm, - frm_vcard and frm_vcard.random_uid or '', - to, sendmail.split('@')[-1])) + sys.stderr.write(_('SendMail: from %s (%s), to %s via %s\n') + % (frm, frm_vcard and frm_vcard.random_uid or '', + to, route_description)) sm_write = sm_close = lambda: True - mark(_('Connecting to %s') % sendmail.split('@')[-1], events) + mark(_('Sending via %s') % route_description, events) - if sendmail.startswith('|'): - sendmail %= {"rcpt": ",".join(to)} - cmd = sendmail[1:].strip().split() + if route['command']: + # Note: The .strip().split() here converts our cmd into a list, + # which should ensure that Popen does not spawn a shell + # with potentially exploitable arguments. + cmd = (route['command'] % {"rcpt": ",".join(to)}).strip().split() proc = Popen(cmd, stdin=PIPE, long_running=True) sm_startup = None sm_write = proc.stdin.write @@ -213,19 +211,11 @@ def sm_close(): ev.data['proto'] = 'subprocess' ev.data['command'] = cmd[0] - elif (sendmail.startswith('smtp:') or - sendmail.startswith('smtorp:') or - sendmail.startswith('smtpssl:') or - sendmail.startswith('smtptls:')): - proto = sendmail.split(':', 1)[0] - host, port = sendmail.split(':', 1 - )[1].replace('/', '').rsplit(':', 1) + elif route['protocol'] in ('smtp', 'smtorp', 'smtpssl', 'smtptls'): + proto = route['protocol'] + host, port = route['host'], route['port'] + user, pwd = route['username'], route['password'] smtp_ssl = proto in ('smtpssl', ) # FIXME: 'smtorp' - if '@' in host: - userpass, host = host.rsplit('@', 1) - user, pwd = userpass.split(':', 1) - else: - user = pwd = None for ev in events: ev.data['proto'] = proto @@ -242,7 +232,7 @@ def sm_connect_server(): )(local_hostname='mailpile.local', timeout=25) if 'sendmail' in session.config.sys.debug: server.set_debuglevel(1) - if smtp_ssl or sendmail[:7] in ('smtorp', 'smtptls'): + if smtp_ssl or proto in ('smtorp', 'smtptls'): conn_needs = [ConnBroker.OUTGOING_ENCRYPTED] else: conn_needs = [ConnBroker.OUTGOING_SMTP] @@ -260,7 +250,7 @@ def sm_startup(): try: server.starttls() except: - if sendmail.startswith('smtptls'): + if proto == 'smtptls': raise InsecureSmtpError() else: server = sm_connect_server() @@ -268,7 +258,8 @@ def sm_startup(): if user and pwd: try: - server.login(user.encode('utf-8'), pwd.encode('utf-8')) + server.login(user.encode('utf-8'), + pwd.encode('utf-8')) except UnicodeDecodeError: fail(_('Bad character in username or password'), events, @@ -308,8 +299,7 @@ def sm_cleanup(): if hasattr(server, 'sock'): server.close() else: - fail(_('Invalid sendmail command/SMTP server: %s') % sendmail, - events) + fail(_('Invalid route: %s') % route, events) try: # Run the entire connect/login sequence in a single timer, but From 6e443d5ad73cc2beed0ea45294221b0af89eadd6 Mon Sep 17 00:00:00 2001 From: "Bjarni R. Einarsson" Date: Sat, 8 Aug 2015 11:26:00 +0100 Subject: [PATCH 6/6] Provide basic notifications for sending progress, fixes #735 --- mailpile/plugins/compose.py | 18 ++++++++++++++++-- .../default/html/jsapi/global/notifications.js | 9 +++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/mailpile/plugins/compose.py b/mailpile/plugins/compose.py index 8c8be4f02..acbe0def8 100644 --- a/mailpile/plugins/compose.py +++ b/mailpile/plugins/compose.py @@ -902,7 +902,8 @@ def command(self, emails=None): # FIXME: Also fatal, when the SMTP server REJECTS the mail except: # We want to try that again! - message = _('Failed to send %s') % email + subject = email.get_msg_info(config.index.MSG_SUBJECT) + message = _('Failed to send message: %s') % subject for ev in events: ev.flags = Event.INCOMPLETE ev.message = message @@ -1032,14 +1033,27 @@ def command(self): if not idx: return self._error(_('The index is not ready yet')) + # Collect a list of messages from the outbox messages = [] for tag in cfg.get_tags(type='outbox'): search = ['in:%s' % tag._key] for msg_idx_pos in idx.search(self.session, search, order='flat-index').as_set(): messages.append('=%s' % b36(msg_idx_pos)) + + # Messages no longer in the outbox get their events canceled... + if cfg.event_log: + events = cfg.event_log.incomplete(source='.plugins.compose.Sendit') + for ev in events: + if ('mid' in ev.data and + ('=%s' % ev.data['mid']) not in messages): + ev.flags = ev.COMPLETE + ev.message = _('Sending cancelled.') + cfg.event_log.log_event(ev) + + # Send all the mail! if messages: - self.args = tuple(messages) + self.args = tuple(set(messages)) return Sendit.command(self) else: return self._success(_('The outbox is empty')) diff --git a/mailpile/www/default/html/jsapi/global/notifications.js b/mailpile/www/default/html/jsapi/global/notifications.js index 49e6f60cf..dfba4a2b2 100644 --- a/mailpile/www/default/html/jsapi/global/notifications.js +++ b/mailpile/www/default/html/jsapi/global/notifications.js @@ -187,3 +187,12 @@ EventLog.subscribe('.*mail_source.*', function(ev) { Mailpile.notification(ev); } }); +EventLog.subscribe('.*compose.Sendit', function(ev) { + if (ev.data.delivered == ev.data.recipients) { + ev.icon = 'icon-outbox'; + } + else if (ev.data.last_error) { + ev.icon = 'icon-signature-unknown'; + } + Mailpile.notification(ev); +});