def SendMail(session, msg_mid, from_to_msg_ev_tuples): routes = _RouteTuples(session, from_to_msg_ev_tuples) # Randomize order of routes, so we don't always try the broken # one first. Any failure will bail out, but we do keep track of # our successes via. the event, so eventually everything sendable # should get sent. routes.sort(key=lambda k: random.randint(0, 10)) # Update initial event state before we go through and start # trying to deliver stuff. for frm, sendmail, 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 ev in events: ev.data['recipients'] = len(ev.private_data.keys()) ev.data['delivered'] = len( [k for k in ev.private_data if ev.private_data[k]]) def mark(msg, events, log=True): for ev in events: ev.flags = Event.RUNNING ev.message = msg if log: session.config.event_log.log_event(ev) session.ui.mark(msg) def fail(msg, events): mark(msg, events, log=True) for ev in events: ev.data['last_error'] = msg raise SendMailError(msg) def smtp_do_or_die(msg, events, method, *args, **kwargs): rc, msg = method(*args, **kwargs) if rc != 250: fail(msg + ' (%s %s)' % (rc, msg), events) # Do the actual delivering... for frm, sendmail, to, msg, events in routes: frm_vcard = session.config.vcards.get_vcard(frm) 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])) sm_write = sm_close = lambda: True mark(_('Connecting to %s') % sendmail.split('@')[-1], events) if sendmail.startswith('|'): sendmail %= {"rcpt": ",".join(to)} cmd = sendmail[1:].strip().split() proc = Popen(cmd, stdin=PIPE, long_running=True) sm_startup = None sm_write = proc.stdin.write def sm_close(): proc.stdin.close() rv = proc.wait() if rv != 0: fail(_('%s failed with exit code %d') % (cmd, rv), events) sm_cleanup = lambda: [proc.stdin.close(), proc.wait()] # FIXME: Update session UI with progress info for ev in events: 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) 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 ev.data['host'] = host ev.data['auth'] = bool(user and pwd) if 'sendmail' in session.config.sys.debug: sys.stderr.write( _('SMTP connection to: %s:%s as %s\n') % (host, port, user or '(anon)')) server = (smtp_ssl and SMTP_SSL or SMTP)(local_hostname='mailpile.local', timeout=25) def sm_startup(): if 'sendmail' in session.config.sys.debug: server.set_debuglevel(1) if proto == 'smtorp': server.connect(host, int(port), socket_cls=session.config.get_tor_socket()) else: server.connect(host, int(port)) if not smtp_ssl: # We always try to enable TLS, even if the user just requested # plain-text smtp. But we only throw errors if the user asked # for encryption. try: server.starttls() except: if sendmail.startswith('smtptls'): raise InsecureSmtpError() if user and pwd: try: server.login(user, pwd) except smtplib.SMTPAuthenticationError: fail(_('Invalid username or password'), events) smtp_do_or_die(_('Sender rejected by SMTP server'), events, server.mail, frm) for rcpt in to: rc, msg = server.rcpt(rcpt) if (rc == SMTORP_HASHCASH_RCODE and msg.startswith(SMTORP_HASHCASH_PREFIX)): rc, msg = server.rcpt(SMTorP_HashCash(rcpt, msg)) if rc != 250: fail(_('Server rejected recpient: %s') % rcpt, events) rcode, rmsg = server.docmd('DATA') if rcode != 354: fail(_('Server rejected DATA: %s %s') % (rcode, rmsg)) def sm_write(data): for line in data.splitlines(True): if line.startswith('.'): server.send('.') server.send(line) def sm_close(): server.send('\r\n.\r\n') smtp_do_or_die(_('Error spooling mail'), events, server.getreply) def sm_cleanup(): if hasattr(server, 'sock'): server.close() else: fail(_('Invalid sendmail command/SMTP server: %s') % sendmail) try: # Run the entire connect/login sequence in a single timer... if sm_startup: RunTimed(30, sm_startup) mark(_('Preparing message...'), events) msg_string = MessageAsString(CleanMessage(session.config, msg)) total = len(msg_string) while msg_string: if mailpile.util.QUITTING: raise TimedOut(_('Quitting')) mark(('Sending message... (%d%%)') % (100 * (total - len(msg_string)) / total), events, log=False) RunTimed(20, sm_write, msg_string[:20480]) msg_string = msg_string[20480:] RunTimed(10, sm_close) mark( _n('Message sent, %d byte', 'Message sent, %d bytes', total) % total, events) for ev in events: for rcpt in to: vcard = session.config.vcards.get_vcard(rcpt) if vcard: vcard.record_history('send', time.time(), msg_mid) if frm_vcard: vcard.prefer_sender(rcpt, frm_vcard) vcard.save() ev.private_data['>'.join([frm, rcpt])] = True ev.data['bytes'] = total ev.data['delivered'] = len( [k for k in ev.private_data if ev.private_data[k]]) finally: sm_cleanup()
def SendMail(session, from_to_msg_tuples): # FIXME: We need to handle the case where we are retrying a message # that we could not deliver before. Some of the tuples may # have been delivered already, and some not. #eventlog for frm, sendmail, to, msg in _RouteTuples(session, from_to_msg_tuples): if 'sendmail' in session.config.sys.debug: sys.stderr.write( _('SendMail: from %s, to %s via %s\n') % (frm, to, sendmail)) sm_write = sm_close = lambda: True session.ui.mark(_('Connecting to %s') % sendmail) if sendmail.startswith('|'): cmd = sendmail[1:].strip().split() proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) sm_write = proc.stdin.write sm_close = proc.stdin.close sm_cleanup = lambda: proc.wait() # FIXME: Update session UI with progress info 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) smtp_ssl = proto in ('smtpssl', ) # FIXME: 'smtorp' if '@' in host: userpass, host = host.rsplit('@', 1) user, pwd = userpass.split(':', 1) else: user = pwd = None if 'sendmail' in session.config.sys.debug: sys.stderr.write( _('SMTP connection to: %s:%s as %s@%s\n') % (host, port, user, pwd)) server = smtp_ssl and SMTP_SSL() or SMTP() if proto == 'smtorp': server.connect(host, int(port), socket_cls=session.config.get_tor_socket()) else: server.connect(host, int(port)) server.ehlo() if not smtp_ssl: # We always try to enable TLS, even if the user just requested # plain-text smtp. But we only throw errors if the user asked # for encryption. try: server.starttls() server.ehlo() except: if sendmail.startswith('smtptls'): raise InsecureSmtpError() if user and pwd: server.login(user, pwd) server.mail(frm) for rcpt in to: server.rcpt(rcpt) server.docmd('DATA') def sender(data): for line in data.splitlines(1): if line.startswith('.'): server.send('.') server.send(line) def closer(): server.send('\r\n.\r\n') server.quit() sm_write = sender sm_close = closer sm_cleanup = lambda: True else: raise Exception( _('Invalid sendmail command/SMTP server: %s') % sendmail) session.ui.mark(_('Preparing message...')) msg_string = MessageAsString(CleanMessage(session.config, msg)) total = len(msg_string) while msg_string: sm_write(msg_string[:20480]) msg_string = msg_string[20480:] session.ui.mark(('Sending message... (%d%%)') % (100 * (total - len(msg_string)) / total)) sm_close() sm_cleanup() session.ui.mark(_('Message sent, %d bytes') % total)
def SendMail(session, from_to_msg_ev_tuples): routes = _RouteTuples(session, from_to_msg_ev_tuples) # Randomize order of routes, so we don't always try the broken # one first. Any failure will bail out, but we do keep track of # our successes via. the event, so eventually everything sendable # should get sent. routes.sort(key=lambda k: random.randint(0, 10)) # Update initial event state before we go through and start # trying to deliver stuff. for frm, sendmail, 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 ev in events: ev.data['recipients'] = len(ev.private_data.keys()) ev.data['delivered'] = len( [k for k in ev.private_data if ev.private_data[k]]) def mark(msg, events, log=True): for ev in events: ev.flags = Event.RUNNING ev.message = msg if log: session.config.event_log.log_event(ev) session.ui.mark(msg) # Do the actual delivering... for frm, sendmail, to, msg, events in routes: if 'sendmail' in session.config.sys.debug: sys.stderr.write( _('SendMail: from %s, to %s via %s\n') % (frm, to, sendmail)) sm_write = sm_close = lambda: True mark(_('Connecting to %s') % sendmail, events) if sendmail.startswith('|'): sendmail %= {"rcpt": ",".join(to)} cmd = sendmail[1:].strip().split() proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) sm_write = proc.stdin.write sm_close = proc.stdin.close sm_cleanup = lambda: proc.wait() # FIXME: Update session UI with progress info for ev in events: 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) 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 ev.data['host'] = host ev.data['auth'] = bool(user and pwd) if 'sendmail' in session.config.sys.debug: sys.stderr.write( _('SMTP connection to: %s:%s as %s@%s\n') % (host, port, user, pwd)) server = smtp_ssl and SMTP_SSL() or SMTP() if proto == 'smtorp': server.connect(host, int(port), socket_cls=session.config.get_tor_socket()) else: server.connect(host, int(port)) server.ehlo() if not smtp_ssl: # We always try to enable TLS, even if the user just requested # plain-text smtp. But we only throw errors if the user asked # for encryption. try: server.starttls() server.ehlo() except: if sendmail.startswith('smtptls'): raise InsecureSmtpError() if user and pwd: server.login(user, pwd) server.mail(frm) for rcpt in to: server.rcpt(rcpt) server.docmd('DATA') def sender(data): for line in data.splitlines(1): if line.startswith('.'): server.send('.') server.send(line) def closer(): server.send('\r\n.\r\n') server.quit() sm_write = sender sm_close = closer sm_cleanup = lambda: True else: raise Exception( _('Invalid sendmail command/SMTP server: %s') % sendmail) mark(_('Preparing message...'), events) msg_string = MessageAsString(CleanMessage(session.config, msg)) total = len(msg_string) while msg_string: sm_write(msg_string[:20480]) msg_string = msg_string[20480:] mark(('Sending message... (%d%%)') % (100 * (total - len(msg_string)) / total), events, log=False) sm_close() sm_cleanup() for ev in events: for rcpt in to: ev.private_data['>'.join([frm, rcpt])] = True ev.data['bytes'] = total ev.data['delivered'] = len( [k for k in ev.private_data if ev.private_data[k]]) mark( _n('Message sent, %d byte', 'Message sent, %d bytes', total) % total, events)
def SendMail(session, msg_mid, from_to_msg_ev_tuples, test_only=False, test_route=None): routes = _RouteTuples(session, from_to_msg_ev_tuples, test_route=test_route) # Randomize order of routes, so we don't always try the broken # one first. Any failure will bail out, but we do keep track of # our successes via. the event, so eventually everything sendable # should get sent. routes.sort(key=lambda k: random.randint(0, 10)) # Update initial event state before we go through and start # trying to deliver stuff. 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, 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 if ev.private_data[k]]) def mark(msg, events, log=True): for ev in events: ev.flags = Event.RUNNING ev.message = msg if log: session.config.event_log.log_event(ev) session.ui.mark(msg) def fail(msg, events, details=None): mark(msg, events, log=True) for ev in events: ev.data['last_error'] = msg raise SendMailError(msg, details=details) def smtp_do_or_die(msg, events, method, *args, **kwargs): rc, msg = method(*args, **kwargs) if rc != 250: fail(msg + ' (%s %s)' % (rc, msg), events, details={'smtp_error': '%s: %s' % (rc, msg)}) # Do the actual delivering... 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, route_description)) sm_write = sm_close = lambda: True mark(_('Sending via %s') % route_description, events) 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 def sm_close(): proc.stdin.close() rv = proc.wait() if rv != 0: fail(_('%s failed with exit code %d') % (cmd, rv), events, details={ 'failed_command': cmd, 'exit_code': rv }) sm_cleanup = lambda: [proc.stdin.close(), proc.wait()] # FIXME: Update session UI with progress info for ev in events: ev.data['proto'] = 'subprocess' ev.data['command'] = cmd[0] 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' for ev in events: ev.data['proto'] = proto ev.data['host'] = host ev.data['auth'] = bool(user and pwd) if 'sendmail' in session.config.sys.debug: sys.stderr.write( _('SMTP connection to: %s:%s as %s\n') % (host, port, user or '(anon)')) serverbox = [None] def sm_connect_server(): server = (smtp_ssl and SMTP_SSL or SMTP)(local_hostname='mailpile.local', timeout=25) if 'sendmail' in session.config.sys.debug: server.set_debuglevel(1) if smtp_ssl or proto in ('smtorp', 'smtptls'): conn_needs = [ConnBroker.OUTGOING_ENCRYPTED] else: conn_needs = [ConnBroker.OUTGOING_SMTP] try: with ConnBroker.context(need=conn_needs) as ctx: server.connect(host, int(port)) server.ehlo_or_helo_if_needed() except (IOError, OSError, smtplib.SMTPServerDisconnected): fail(_('Failed to connect to %s') % host, events, details={'connection_error': True}) return server def sm_startup(): server = sm_connect_server() if not smtp_ssl: # We always try to enable TLS, even if the user just # requested plain-text smtp. But we only throw errors # if the user asked for encryption. try: server.starttls() server.ehlo_or_helo_if_needed() except: if proto == 'smtptls': raise InsecureSmtpError() else: server = sm_connect_server() serverbox[0] = server if user and pwd: try: server.login(user.encode('utf-8'), pwd.encode('utf-8')) except UnicodeDecodeError: fail(_('Bad character in username or password'), events, details={'authentication_error': True}) except smtplib.SMTPAuthenticationError: fail(_('Invalid username or password'), events, details={'authentication_error': True}) except smtplib.SMTPException: fail(_('Authentication not supported'), events, details={'authentication_error': True}) smtp_do_or_die(_('Sender rejected by SMTP server'), events, server.mail, frm) for rcpt in to: rc, msg = server.rcpt(rcpt) if (rc == SMTORP_HASHCASH_RCODE and msg.startswith(SMTORP_HASHCASH_PREFIX)): rc, msg = server.rcpt(SMTorP_HashCash(rcpt, msg)) if rc != 250: fail(_('Server rejected recipient: %s') % rcpt, events) rcode, rmsg = server.docmd('DATA') if rcode != 354: fail(_('Server rejected DATA: %s %s') % (rcode, rmsg)) def sm_write(data): server = serverbox[0] for line in data.splitlines(True): if line.startswith('.'): server.send('.') server.send(line) def sm_close(): server = serverbox[0] server.send('\r\n.\r\n') smtp_do_or_die(_('Error spooling mail'), events, server.getreply) def sm_cleanup(): server = serverbox[0] if hasattr(server, 'sock'): server.close() else: fail(_('Invalid route: %s') % route, events) try: # Run the entire connect/login sequence in a single timer, but # give it plenty of time in case the network is lame. if sm_startup: RunTimed(300, sm_startup) if test_only: return True mark(_('Preparing message...'), events) msg_string = MessageAsString(CleanMessage(session.config, msg)) total = len(msg_string) while msg_string: if mailpile.util.QUITTING: raise TimedOut(_('Quitting')) mark(('Sending message... (%d%%)') % (100 * (total - len(msg_string)) / total), events, log=False) RunTimed(120, sm_write, msg_string[:20480]) msg_string = msg_string[20480:] RunTimed(30, sm_close) mark( _n('Message sent, %d byte', 'Message sent, %d bytes', total) % total, events) for ev in events: for rcpt in to: vcard = session.config.vcards.get_vcard(rcpt) if vcard: vcard.record_history('send', time.time(), msg_mid) if frm_vcard: vcard.prefer_sender(rcpt, frm_vcard) if update_to_vcards: vcard.pgp_key_shared = int(time.time()) vcard.save() ev.private_data['>'.join([frm, rcpt])] = True ev.data['bytes'] = total ev.data['delivered'] = len( [k for k in ev.private_data if ev.private_data[k]]) finally: sm_cleanup() return True