1 # -*- test-case-name: twisted.mail.test.test_options -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
7 I am the support module for creating mail servers with twistd
13 from twisted.mail import mail
14 from twisted.mail import maildir
15 from twisted.mail import relay
16 from twisted.mail import relaymanager
17 from twisted.mail import alias
19 from twisted.mail.protocols import SSLContextFactory
21 from twisted.internet import endpoints
23 from twisted.python import usage
24 from twisted.python import deprecate
25 from twisted.python import versions
27 from twisted.cred import checkers
28 from twisted.cred import strcred
30 from twisted.application import internet
33 class Options(usage.Options, strcred.AuthOptionMixin):
34 synopsis = "[options]"
38 "Port to start the POP3-over-SSL server on (0 to disable). "
40 "'--pop3 ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
42 ["certificate", "c", None,
43 "Certificate file to use for SSL connections. "
45 "'--pop3 ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
48 "Relay messages according to their envelope 'To', using "
49 "the given path as a queue directory."],
51 ["hostname", "H", None,
52 "The hostname by which to identify this server."],
56 ["esmtp", "E", "Use RFC 1425/1869 SMTP extensions"],
57 ["disable-anonymous", None,
58 "Disallow non-authenticated SMTP connections"],
59 ["no-pop3", None, "Disable the default POP3 server."],
60 ["no-smtp", None, "Disable the default SMTP server."],
68 compData = usage.Completions(
69 optActions={"hostname" : usage.CompleteHostnames(),
70 "certificate" : usage.CompleteFiles("*.pem")}
73 longdesc = "This creates a mail.tap file that can be used by twistd."
76 usage.Options.__init__(self)
77 self.service = mail.MailService()
78 self.last_domain = None
79 for service in self._protoDefaults:
83 def addEndpoint(self, service, description, certificate=None):
85 Given a 'service' (pop3 or smtp), add an endpoint.
88 _toEndpoint(description, certificate=certificate))
91 def opt_pop3(self, description):
93 Add a pop3 port listener on the specified endpoint. You can listen on
94 multiple ports by specifying multiple --pop3 options. For backwards
95 compatibility, a bare TCP port number can be specified, but this is
96 deprecated. [SSL Example: ssl:8995:privateKey=mycert.pem] [default:
99 self.addEndpoint('pop3', description)
103 def opt_smtp(self, description):
105 Add an smtp port listener on the specified endpoint. You can listen on
106 multiple ports by specifying multiple --smtp options For backwards
107 compatibility, a bare TCP port number can be specified, but this is
108 deprecated. [SSL Example: ssl:8465:privateKey=mycert.pem] [default:
111 self.addEndpoint('smtp', description)
115 def opt_passwordfile(self, filename):
117 Specify a file containing username:password login info for authenticated
118 ESMTP connections. (DEPRECATED; see --help-auth instead)
120 ch = checkers.OnDiskUsernamePasswordDatabase(filename)
121 self.service.smtpPortal.registerChecker(ch)
122 msg = deprecate.getDeprecationWarningString(
123 self.opt_passwordfile, versions.Version('twisted.mail', 11, 0, 0))
124 warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
125 opt_P = opt_passwordfile
128 def opt_default(self):
129 """Make the most recently specified domain the default domain."""
131 self.service.addDomain('', self.last_domain)
133 raise usage.UsageError("Specify a domain before specifying using --default")
137 def opt_maildirdbmdomain(self, domain):
138 """generate an SMTP/POP3 virtual domain which saves to \"path\"
141 name, path = domain.split('=')
143 raise usage.UsageError("Argument to --maildirdbmdomain must be of the form 'name=path'")
145 self.last_domain = maildir.MaildirDirdbmDomain(self.service, os.path.abspath(path))
146 self.service.addDomain(name, self.last_domain)
147 opt_d = opt_maildirdbmdomain
149 def opt_user(self, user_pass):
150 """add a user/password to the last specified domains
153 user, password = user_pass.split('=', 1)
155 raise usage.UsageError("Argument to --user must be of the form 'user=password'")
157 self.last_domain.addUser(user, password)
159 raise usage.UsageError("Specify a domain before specifying users")
162 def opt_bounce_to_postmaster(self):
163 """undelivered mails are sent to the postmaster
165 self.last_domain.postmaster = 1
166 opt_b = opt_bounce_to_postmaster
168 def opt_aliases(self, filename):
169 """Specify an aliases(5) file to use for this domain"""
170 if self.last_domain is not None:
171 if mail.IAliasableDomain.providedBy(self.last_domain):
172 aliases = alias.loadAliasFile(self.service.domains, filename)
173 self.last_domain.setAliasGroup(aliases)
174 self.service.monitor.monitorFile(
176 AliasUpdater(self.service.domains, self.last_domain)
179 raise usage.UsageError(
180 "%s does not support alias files" % (
181 self.last_domain.__class__.__name__,
185 raise usage.UsageError("Specify a domain before specifying aliases")
188 def _getEndpoints(self, reactor, service):
190 Return a list of endpoints for the specified service, constructing
191 defaults if necessary.
193 @param reactor: If any endpoints are created, this is the reactor with
194 which they are created.
196 @param service: A key into self indicating the type of service to
197 retrieve endpoints for. This is either C{"pop3"} or C{"smtp"}.
199 @return: A C{list} of C{IServerStreamEndpoint} providers corresponding
200 to the command line parameters that were specified for C{service}.
201 If none were and the protocol was not explicitly disabled with a
202 I{--no-*} option, a default endpoint for the service is created
203 using C{self._protoDefaults}.
205 if service == 'pop3' and self['pop3s'] and len(self[service]) == 1:
206 # The single endpoint here is the POP3S service we added in
207 # postOptions. Include the default endpoint alongside it.
208 return self[service] + [
209 endpoints.TCP4ServerEndpoint(
210 reactor, self._protoDefaults[service])]
212 # For any non-POP3S case, if there are any services set up, just
215 elif self['no-' + service]:
216 # If there are no services, but the service was explicitly disabled,
220 # Otherwise, return the old default service.
222 endpoints.TCP4ServerEndpoint(
223 reactor, self._protoDefaults[service])]
226 def postOptions(self):
227 from twisted.internet import reactor
230 if not self['certificate']:
231 raise usage.UsageError("Cannot specify --pop3s without "
233 elif not os.path.exists(self['certificate']):
234 raise usage.UsageError("Certificate file %r does not exist."
235 % self['certificate'])
238 'pop3', self['pop3s'], certificate=self['certificate'])
240 if self['esmtp'] and self['hostname'] is None:
241 raise usage.UsageError("--esmtp requires --hostname")
243 # If the --auth option was passed, this will be present -- otherwise,
244 # it won't be, which is also a perfectly valid state.
245 if 'credCheckers' in self:
246 for ch in self['credCheckers']:
247 self.service.smtpPortal.registerChecker(ch)
249 if not self['disable-anonymous']:
250 self.service.smtpPortal.registerChecker(checkers.AllowAnonymousAccess())
253 for service in self._protoDefaults:
254 self[service] = self._getEndpoints(reactor, service)
259 raise usage.UsageError("You cannot disable all protocols")
264 def __init__(self, domains, domain):
265 self.domains = domains
267 def __call__(self, new):
268 self.domain.setAliasGroup(alias.loadAliasFile(self.domains, new))
271 def _toEndpoint(description, certificate=None):
273 Tries to guess whether a description is a bare TCP port or a endpoint. If a
274 bare port is specified and a certificate file is present, returns an
275 SSL4ServerEndpoint and otherwise returns a TCP4ServerEndpoint.
277 from twisted.internet import reactor
279 port = int(description)
281 return endpoints.serverFromString(reactor, description)
284 "Specifying plain ports and/or a certificate is deprecated since "
285 "Twisted 11.0; use endpoint descriptions instead.",
286 category=DeprecationWarning, stacklevel=3)
289 ctx = SSLContextFactory(certificate)
290 return endpoints.SSL4ServerEndpoint(reactor, port, ctx)
291 return endpoints.TCP4ServerEndpoint(reactor, port)
294 def makeService(config):
296 Construct a service for operating a mail server.
298 The returned service may include POP3 servers or SMTP servers (or both),
299 depending on the configuration passed in. If there are multiple servers,
300 they will share all of their non-network state (eg, the same user accounts
301 are available on all of them).
303 @param config: An L{Options} instance specifying what servers to include in
304 the returned service and where they should keep mail data.
306 @return: An L{IService} provider which contains the requested mail servers.
309 rmType = relaymanager.SmartHostESMTPRelayingManager
310 smtpFactory = config.service.getESMTPFactory
312 rmType = relaymanager.SmartHostSMTPRelayingManager
313 smtpFactory = config.service.getSMTPFactory
316 dir = config['relay']
317 if not os.path.isdir(dir):
320 config.service.setQueue(relaymanager.Queue(dir))
321 default = relay.DomainQueuer(config.service)
323 manager = rmType(config.service.queue)
325 manager.fArgs += (None, None)
326 manager.fArgs += (config['hostname'],)
328 helper = relaymanager.RelayStateHelper(manager, 1)
329 helper.setServiceParent(config.service)
330 config.service.domains.setDefaultDomain(default)
333 f = config.service.getPOP3Factory()
334 for endpoint in config['pop3']:
335 svc = internet.StreamServerEndpointService(endpoint, f)
336 svc.setServiceParent(config.service)
340 if config['hostname']:
341 f.domain = config['hostname']
342 f.fArgs = (f.domain,)
344 f.fArgs = (None, None) + f.fArgs
345 for endpoint in config['smtp']:
346 svc = internet.StreamServerEndpointService(endpoint, f)
347 svc.setServiceParent(config.service)
349 return config.service