Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / mail / tap.py
1 # -*- test-case-name: twisted.mail.test.test_options -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 I am the support module for creating mail servers with twistd
8 """
9
10 import os
11 import warnings
12
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
18
19 from twisted.mail.protocols import SSLContextFactory
20
21 from twisted.internet import endpoints
22
23 from twisted.python import usage
24 from twisted.python import deprecate
25 from twisted.python import versions
26
27 from twisted.cred import checkers
28 from twisted.cred import strcred
29
30 from twisted.application import internet
31
32
33 class Options(usage.Options, strcred.AuthOptionMixin):
34     synopsis = "[options]"
35
36     optParameters = [
37         ["pop3s", "S", 0,
38          "Port to start the POP3-over-SSL server on (0 to disable). "
39          "DEPRECATED: use "
40          "'--pop3 ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
41
42         ["certificate", "c", None,
43          "Certificate file to use for SSL connections. "
44          "DEPRECATED: use "
45          "'--pop3 ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
46
47         ["relay", "R", None,
48          "Relay messages according to their envelope 'To', using "
49          "the given path as a queue directory."],
50
51         ["hostname", "H", None,
52          "The hostname by which to identify this server."],
53     ]
54
55     optFlags = [
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."],
61     ]
62
63     _protoDefaults = {
64         "pop3": 8110,
65         "smtp": 8025,
66     }
67
68     compData = usage.Completions(
69                    optActions={"hostname" : usage.CompleteHostnames(),
70                                "certificate" : usage.CompleteFiles("*.pem")}
71                    )
72
73     longdesc = "This creates a mail.tap file that can be used by twistd."
74
75     def __init__(self):
76         usage.Options.__init__(self)
77         self.service = mail.MailService()
78         self.last_domain = None
79         for service in self._protoDefaults:
80             self[service] = []
81
82
83     def addEndpoint(self, service, description, certificate=None):
84         """
85         Given a 'service' (pop3 or smtp), add an endpoint.
86         """
87         self[service].append(
88             _toEndpoint(description, certificate=certificate))
89
90
91     def opt_pop3(self, description):
92         """
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:
97         tcp:8110]
98         """
99         self.addEndpoint('pop3', description)
100     opt_p = opt_pop3
101
102
103     def opt_smtp(self, description):
104         """
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:
109         tcp:8025]
110         """
111         self.addEndpoint('smtp', description)
112     opt_s = opt_smtp
113
114
115     def opt_passwordfile(self, filename):
116         """
117         Specify a file containing username:password login info for authenticated
118         ESMTP connections. (DEPRECATED; see --help-auth instead)
119         """
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
126
127
128     def opt_default(self):
129         """Make the most recently specified domain the default domain."""
130         if self.last_domain:
131             self.service.addDomain('', self.last_domain)
132         else:
133             raise usage.UsageError("Specify a domain before specifying using --default")
134     opt_D = opt_default
135
136
137     def opt_maildirdbmdomain(self, domain):
138         """generate an SMTP/POP3 virtual domain which saves to \"path\"
139         """
140         try:
141             name, path = domain.split('=')
142         except ValueError:
143             raise usage.UsageError("Argument to --maildirdbmdomain must be of the form 'name=path'")
144
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
148
149     def opt_user(self, user_pass):
150         """add a user/password to the last specified domains
151         """
152         try:
153             user, password = user_pass.split('=', 1)
154         except ValueError:
155             raise usage.UsageError("Argument to --user must be of the form 'user=password'")
156         if self.last_domain:
157             self.last_domain.addUser(user, password)
158         else:
159             raise usage.UsageError("Specify a domain before specifying users")
160     opt_u = opt_user
161
162     def opt_bounce_to_postmaster(self):
163         """undelivered mails are sent to the postmaster
164         """
165         self.last_domain.postmaster = 1
166     opt_b = opt_bounce_to_postmaster
167
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(
175                     filename,
176                     AliasUpdater(self.service.domains, self.last_domain)
177                 )
178             else:
179                 raise usage.UsageError(
180                     "%s does not support alias files" % (
181                         self.last_domain.__class__.__name__,
182                     )
183                 )
184         else:
185             raise usage.UsageError("Specify a domain before specifying aliases")
186     opt_A = opt_aliases
187
188     def _getEndpoints(self, reactor, service):
189         """
190         Return a list of endpoints for the specified service, constructing
191         defaults if necessary.
192
193         @param reactor: If any endpoints are created, this is the reactor with
194             which they are created.
195
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"}.
198
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}.
204         """
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])]
211         elif self[service]:
212             # For any non-POP3S case, if there are any services set up, just
213             # return those.
214             return self[service]
215         elif self['no-' + service]:
216             # If there are no services, but the service was explicitly disabled,
217             # return nothing.
218             return []
219         else:
220             # Otherwise, return the old default service.
221             return [
222                 endpoints.TCP4ServerEndpoint(
223                     reactor, self._protoDefaults[service])]
224
225
226     def postOptions(self):
227         from twisted.internet import reactor
228
229         if self['pop3s']:
230             if not self['certificate']:
231                 raise usage.UsageError("Cannot specify --pop3s without "
232                                        "--certificate")
233             elif not os.path.exists(self['certificate']):
234                 raise usage.UsageError("Certificate file %r does not exist."
235                                        % self['certificate'])
236             else:
237                 self.addEndpoint(
238                     'pop3', self['pop3s'], certificate=self['certificate'])
239
240         if self['esmtp'] and self['hostname'] is None:
241             raise usage.UsageError("--esmtp requires --hostname")
242
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)
248
249         if not self['disable-anonymous']:
250             self.service.smtpPortal.registerChecker(checkers.AllowAnonymousAccess())
251
252         anything = False
253         for service in self._protoDefaults:
254             self[service] = self._getEndpoints(reactor, service)
255             if self[service]:
256                 anything = True
257
258         if not anything:
259             raise usage.UsageError("You cannot disable all protocols")
260
261
262
263 class AliasUpdater:
264     def __init__(self, domains, domain):
265         self.domains = domains
266         self.domain = domain
267     def __call__(self, new):
268         self.domain.setAliasGroup(alias.loadAliasFile(self.domains, new))
269
270
271 def _toEndpoint(description, certificate=None):
272     """
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.
276     """
277     from twisted.internet import reactor
278     try:
279         port = int(description)
280     except ValueError:
281         return endpoints.serverFromString(reactor, description)
282
283     warnings.warn(
284         "Specifying plain ports and/or a certificate is deprecated since "
285         "Twisted 11.0; use endpoint descriptions instead.",
286         category=DeprecationWarning, stacklevel=3)
287
288     if certificate:
289         ctx = SSLContextFactory(certificate)
290         return endpoints.SSL4ServerEndpoint(reactor, port, ctx)
291     return endpoints.TCP4ServerEndpoint(reactor, port)
292
293
294 def makeService(config):
295     """
296     Construct a service for operating a mail server.
297
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).
302
303     @param config: An L{Options} instance specifying what servers to include in
304         the returned service and where they should keep mail data.
305
306     @return: An L{IService} provider which contains the requested mail servers.
307     """
308     if config['esmtp']:
309         rmType = relaymanager.SmartHostESMTPRelayingManager
310         smtpFactory = config.service.getESMTPFactory
311     else:
312         rmType = relaymanager.SmartHostSMTPRelayingManager
313         smtpFactory = config.service.getSMTPFactory
314
315     if config['relay']:
316         dir = config['relay']
317         if not os.path.isdir(dir):
318             os.mkdir(dir)
319
320         config.service.setQueue(relaymanager.Queue(dir))
321         default = relay.DomainQueuer(config.service)
322
323         manager = rmType(config.service.queue)
324         if config['esmtp']:
325             manager.fArgs += (None, None)
326         manager.fArgs += (config['hostname'],)
327
328         helper = relaymanager.RelayStateHelper(manager, 1)
329         helper.setServiceParent(config.service)
330         config.service.domains.setDefaultDomain(default)
331
332     if config['pop3']:
333         f = config.service.getPOP3Factory()
334         for endpoint in config['pop3']:
335             svc = internet.StreamServerEndpointService(endpoint, f)
336             svc.setServiceParent(config.service)
337
338     if config['smtp']:
339         f = smtpFactory()
340         if config['hostname']:
341             f.domain = config['hostname']
342             f.fArgs = (f.domain,)
343         if config['esmtp']:
344             f.fArgs = (None, None) + f.fArgs
345         for endpoint in config['smtp']:
346             svc = internet.StreamServerEndpointService(endpoint, f)
347             svc.setServiceParent(config.service)
348
349     return config.service