| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
"""I am the support module for creating mail servers with twistd |
|---|
| 7 |
""" |
|---|
| 8 |
|
|---|
| 9 |
import os |
|---|
| 10 |
import sys |
|---|
| 11 |
|
|---|
| 12 |
from twisted.mail import mail |
|---|
| 13 |
from twisted.mail import maildir |
|---|
| 14 |
from twisted.mail import relay |
|---|
| 15 |
from twisted.mail import relaymanager |
|---|
| 16 |
from twisted.mail import alias |
|---|
| 17 |
|
|---|
| 18 |
from twisted.python import usage |
|---|
| 19 |
|
|---|
| 20 |
from twisted.cred import checkers |
|---|
| 21 |
from twisted.application import internet |
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 |
class Options(usage.Options): |
|---|
| 25 |
synopsis = "[options]" |
|---|
| 26 |
|
|---|
| 27 |
optParameters = [ |
|---|
| 28 |
["pop3", "p", 8110, "Port to start the POP3 server on (0 to disable).", usage.portCoerce], |
|---|
| 29 |
["pop3s", "S", 0, "Port to start the POP3-over-SSL server on (0 to disable).", usage.portCoerce], |
|---|
| 30 |
["smtp", "s", 8025, "Port to start the SMTP server on (0 to disable).", usage.portCoerce], |
|---|
| 31 |
["certificate", "c", None, "Certificate file to use for SSL connections"], |
|---|
| 32 |
["relay", "R", None, |
|---|
| 33 |
"Relay messages according to their envelope 'To', using the given" |
|---|
| 34 |
"path as a queue directory."], |
|---|
| 35 |
["hostname", "H", None, "The hostname by which to identify this server."], |
|---|
| 36 |
] |
|---|
| 37 |
|
|---|
| 38 |
optFlags = [ |
|---|
| 39 |
["esmtp", "E", "Use RFC 1425/1869 SMTP extensions"], |
|---|
| 40 |
["disable-anonymous", None, "Disallow non-authenticated SMTP connections"], |
|---|
| 41 |
] |
|---|
| 42 |
zsh_actions = {"hostname" : "_hosts"} |
|---|
| 43 |
|
|---|
| 44 |
longdesc = "This creates a mail.tap file that can be used by twistd." |
|---|
| 45 |
|
|---|
| 46 |
def __init__(self): |
|---|
| 47 |
usage.Options.__init__(self) |
|---|
| 48 |
self.service = mail.MailService() |
|---|
| 49 |
self.last_domain = None |
|---|
| 50 |
|
|---|
| 51 |
def opt_passwordfile(self, filename): |
|---|
| 52 |
"""Specify a file containing username:password login info for authenticated ESMTP connections.""" |
|---|
| 53 |
ch = checkers.OnDiskUsernamePasswordDatabase(filename) |
|---|
| 54 |
self.service.smtpPortal.registerChecker(ch) |
|---|
| 55 |
opt_P = opt_passwordfile |
|---|
| 56 |
|
|---|
| 57 |
def opt_default(self): |
|---|
| 58 |
"""Make the most recently specified domain the default domain.""" |
|---|
| 59 |
if self.last_domain: |
|---|
| 60 |
self.service.addDomain('', self.last_domain) |
|---|
| 61 |
else: |
|---|
| 62 |
raise usage.UsageError("Specify a domain before specifying using --default") |
|---|
| 63 |
opt_D = opt_default |
|---|
| 64 |
|
|---|
| 65 |
def opt_maildirdbmdomain(self, domain): |
|---|
| 66 |
"""generate an SMTP/POP3 virtual domain which saves to \"path\" |
|---|
| 67 |
""" |
|---|
| 68 |
try: |
|---|
| 69 |
name, path = domain.split('=') |
|---|
| 70 |
except ValueError: |
|---|
| 71 |
raise usage.UsageError("Argument to --maildirdbmdomain must be of the form 'name=path'") |
|---|
| 72 |
|
|---|
| 73 |
self.last_domain = maildir.MaildirDirdbmDomain(self.service, os.path.abspath(path)) |
|---|
| 74 |
self.service.addDomain(name, self.last_domain) |
|---|
| 75 |
opt_d = opt_maildirdbmdomain |
|---|
| 76 |
|
|---|
| 77 |
def opt_user(self, user_pass): |
|---|
| 78 |
"""add a user/password to the last specified domains |
|---|
| 79 |
""" |
|---|
| 80 |
try: |
|---|
| 81 |
user, password = user_pass.split('=', 1) |
|---|
| 82 |
except ValueError: |
|---|
| 83 |
raise usage.UsageError("Argument to --user must be of the form 'user=password'") |
|---|
| 84 |
if self.last_domain: |
|---|
| 85 |
self.last_domain.addUser(user, password) |
|---|
| 86 |
else: |
|---|
| 87 |
raise usage.UsageError("Specify a domain before specifying users") |
|---|
| 88 |
opt_u = opt_user |
|---|
| 89 |
|
|---|
| 90 |
def opt_bounce_to_postmaster(self): |
|---|
| 91 |
"""undelivered mails are sent to the postmaster |
|---|
| 92 |
""" |
|---|
| 93 |
self.last_domain.postmaster = 1 |
|---|
| 94 |
opt_b = opt_bounce_to_postmaster |
|---|
| 95 |
|
|---|
| 96 |
def opt_aliases(self, filename): |
|---|
| 97 |
"""Specify an aliases(5) file to use for this domain""" |
|---|
| 98 |
if self.last_domain is not None: |
|---|
| 99 |
if mail.IAliasableDomain.providedBy(self.last_domain): |
|---|
| 100 |
aliases = alias.loadAliasFile(self.service.domains, filename) |
|---|
| 101 |
self.last_domain.setAliasGroup(aliases) |
|---|
| 102 |
self.service.monitor.monitorFile( |
|---|
| 103 |
filename, |
|---|
| 104 |
AliasUpdater(self.service.domains, self.last_domain) |
|---|
| 105 |
) |
|---|
| 106 |
else: |
|---|
| 107 |
raise usage.UsageError( |
|---|
| 108 |
"%s does not support alias files" % ( |
|---|
| 109 |
self.last_domain.__class__.__name__, |
|---|
| 110 |
) |
|---|
| 111 |
) |
|---|
| 112 |
else: |
|---|
| 113 |
raise usage.UsageError("Specify a domain before specifying aliases") |
|---|
| 114 |
opt_A = opt_aliases |
|---|
| 115 |
|
|---|
| 116 |
def postOptions(self): |
|---|
| 117 |
if self['pop3s']: |
|---|
| 118 |
if not self['certificate']: |
|---|
| 119 |
raise usage.UsageError("Cannot specify --pop3s without " |
|---|
| 120 |
"--certificate") |
|---|
| 121 |
elif not os.path.exists(self['certificate']): |
|---|
| 122 |
raise usage.UsageError("Certificate file %r does not exist." |
|---|
| 123 |
% self['certificate']) |
|---|
| 124 |
|
|---|
| 125 |
if not self['disable-anonymous']: |
|---|
| 126 |
self.service.smtpPortal.registerChecker(checkers.AllowAnonymousAccess()) |
|---|
| 127 |
|
|---|
| 128 |
if not (self['pop3'] or self['smtp'] or self['pop3s']): |
|---|
| 129 |
raise usage.UsageError("You cannot disable all protocols") |
|---|
| 130 |
|
|---|
| 131 |
class AliasUpdater: |
|---|
| 132 |
def __init__(self, domains, domain): |
|---|
| 133 |
self.domains = domains |
|---|
| 134 |
self.domain = domain |
|---|
| 135 |
def __call__(self, new): |
|---|
| 136 |
self.domain.setAliasGroup(alias.loadAliasFile(self.domains, new)) |
|---|
| 137 |
|
|---|
| 138 |
def makeService(config): |
|---|
| 139 |
if config['esmtp']: |
|---|
| 140 |
rmType = relaymanager.SmartHostESMTPRelayingManager |
|---|
| 141 |
smtpFactory = config.service.getESMTPFactory |
|---|
| 142 |
else: |
|---|
| 143 |
rmType = relaymanager.SmartHostSMTPRelayingManager |
|---|
| 144 |
smtpFactory = config.service.getSMTPFactory |
|---|
| 145 |
|
|---|
| 146 |
if config['relay']: |
|---|
| 147 |
dir = config['relay'] |
|---|
| 148 |
if not os.path.isdir(dir): |
|---|
| 149 |
os.mkdir(dir) |
|---|
| 150 |
|
|---|
| 151 |
config.service.setQueue(relaymanager.Queue(dir)) |
|---|
| 152 |
default = relay.DomainQueuer(config.service) |
|---|
| 153 |
|
|---|
| 154 |
manager = rmType(config.service.queue) |
|---|
| 155 |
if config['esmtp']: |
|---|
| 156 |
manager.fArgs += (None, None) |
|---|
| 157 |
manager.fArgs += (config['hostname'],) |
|---|
| 158 |
|
|---|
| 159 |
helper = relaymanager.RelayStateHelper(manager, 1) |
|---|
| 160 |
helper.setServiceParent(config.service) |
|---|
| 161 |
config.service.domains.setDefaultDomain(default) |
|---|
| 162 |
|
|---|
| 163 |
ctx = None |
|---|
| 164 |
if config['certificate']: |
|---|
| 165 |
from twisted.mail.protocols import SSLContextFactory |
|---|
| 166 |
ctx = SSLContextFactory(config['certificate']) |
|---|
| 167 |
|
|---|
| 168 |
if config['pop3']: |
|---|
| 169 |
s = internet.TCPServer(config['pop3'], config.service.getPOP3Factory()) |
|---|
| 170 |
s.setServiceParent(config.service) |
|---|
| 171 |
if config['pop3s']: |
|---|
| 172 |
s = internet.SSLServer(config['pop3s'], |
|---|
| 173 |
config.service.getPOP3Factory(), ctx) |
|---|
| 174 |
s.setServiceParent(config.service) |
|---|
| 175 |
if config['smtp']: |
|---|
| 176 |
f = smtpFactory() |
|---|
| 177 |
f.context = ctx |
|---|
| 178 |
if config['hostname']: |
|---|
| 179 |
f.domain = config['hostname'] |
|---|
| 180 |
f.fArgs = (f.domain,) |
|---|
| 181 |
if config['esmtp']: |
|---|
| 182 |
f.fArgs = (None, None) + f.fArgs |
|---|
| 183 |
s = internet.TCPServer(config['smtp'], f) |
|---|
| 184 |
s.setServiceParent(config.service) |
|---|
| 185 |
return config.service |
|---|