~mmlmtp/mailman/mm3lmtp

« back to all changes in this revision

Viewing changes to mailman/queue/lmtp.py

  • Committer: William Mead
  • Date: 2008-07-18 10:57:47 UTC
  • Revision ID: wam22@quant.staff.uscs.susx.ac.uk-20080718105747-b2x5oqdub2bifxxo
rev8:fixed Doctest, code respects rfcs

Show diffs side-by-side

added added

removed removed

Lines of Context:
66
66
DASH    = '-'
67
67
CRLF    = '\r\n'
68
68
# These error codes are now obsolete. The LMTP protocol now uses enhanced error codes 
69
 
ERR_451 = '451 Requested action aborted: error in processing'
70
 
ERR_501 = '501 Message has defects'
71
 
ERR_502 = '502 Error: command HELO not implemented'
72
 
# ERR_550 = config.LMTP_ERR_550
 
69
#ERR_451 = '451 Requested action aborted: error in processing'
 
70
#ERR_501 = '501 Message has defects'
 
71
#ERR_502 = '502 Error: command HELO not implemented'
 
72
#ERR_550 = config.LMTP_ERR_550
73
73
 
74
74
# Enhanced error codes
75
75
EERR_200 = '2.0.0'
116
116
 
117
117
class Channel(asynchat.async_chat):
118
118
    """An LMTP channel."""
119
 
    # The LMTP channel is not dependent on the SMTP channel in Python smtpd, 
 
119
    # The LMTP channel is not dependent on the SMTP channel found in Python smtpd, 
120
120
    # It is a complete working LMTP channel, based on smtpd in Python
121
121
    
122
122
    COMMAND = 0
124
124
    # The LHLO boolean determines if the LHLO command has been used or not during a session
125
125
    # False = LHLO has not been used
126
126
    # True = LHLO has been used
127
 
    # RFC 2033 requires the client to say lhlo to the server before mail can be sent
 
127
    # RFC 2033 requires the client to say LHLO to the server before mail can be sent
128
128
    LHLO = False
129
129
 
130
130
    def __init__(self, server, conn, addr):
141
141
        self._fqdn = socket.getfqdn()
142
142
        self._peer = conn.getpeername()
143
143
        self.set_terminator('\r\n')
144
 
        self.push('220 '+EERR_200+' '+self._fqdn+' '+__version__)
 
144
        self.push('220 %s %s' % (self._fqdn, __version__))
145
145
 
146
146
 
147
147
    # smtp_HELO pushs an error if the HELO command is used
148
148
    def smtp_HELO(self, arg):
149
 
        self.push('501 '+EERR_551+'. Use: LHLO command')
 
149
        self.push('501 '+EERR_551+' Use: LHLO command')
150
150
        return
151
151
 
152
152
    # smtp_EHLO pushs an error if the EHLO command is used
153
153
    def smtp_EHLO(self, arg):
154
 
        self.push('501 '+EERR_551+'. Use: LHLO command')
 
154
        self.push('501 '+EERR_551+' Use: LHLO command')
155
155
        return
156
156
 
157
157
    def smtp_LHLO(self, arg):
158
158
        """HELO is not a valid LMTP command."""
159
159
        if not arg:
160
 
            self.push('501 '+EERR_554+'. Syntax: lhlo hostname')
 
160
            self.push('501 '+EERR_554+' Syntax: lhlo hostname')
161
161
            return
162
162
        if self._greeting:
163
 
            self.push('503 '+EERR_551+'. Duplicate LHLO')
 
163
            self.push('503 '+EERR_551+' Duplicate LHLO')
164
164
        else:
165
165
            self.LHLO = True
166
166
            self._greeting = arg
167
 
            self.push('250 '+EERR_200+' '+self._fqdn)
168
 
            self.push('250 '+EERR_200+' PIPELINING')
169
 
            self.push('250 '+EERR_200+' ENHANCEDSTATUSCODES')
 
167
            # Don't forget '-' after the status code on each line 
 
168
            # except for the last line, if there is a multiline response 
 
169
            self.push('250-%s' % self._fqdn)
 
170
            # Use following line when Pipelining is supported
 
171
            #self.push('250-PIPELINING')
 
172
            self.push('250 ENHANCEDSTATUSCODES')
170
173
 
171
174
    def smtp_MAIL(self, arg):
172
175
        if self.LHLO == False:
173
 
            self.push('503 '+EERR_551+'. Need LHLO command')
 
176
            self.push('503 '+EERR_551+' Need LHLO command')
174
177
            return
175
178
        address = self._getaddr('FROM:', arg) if arg else None
176
179
        if not address:
177
 
            self.push('501 '+EERR_554+'. Syntax: MAIL FROM:<address>')
 
180
            self.push('501 '+EERR_554+' Syntax: MAIL FROM:<address>')
178
181
            return
179
182
        if self._mailfrom:
180
 
            self.push('503 '+EERR_551+'. Nested MAIL command')
 
183
            self.push('503 '+EERR_551+' Nested MAIL command')
181
184
            return
182
185
        self._mailfrom = address
183
 
        self.push('250 '+EERR_200+' Ok')
 
186
        self.push('250 '+EERR_200+' Ok Sender address accepted')
184
187
 
185
188
 
186
189
    def smtp_RCPT(self, arg):
187
190
        if not self._mailfrom:
188
 
            self.push('503 '+EERR_551+'. Need MAIL command')
 
191
            self.push('503 '+EERR_551+' Need MAIL command')
189
192
            return
190
193
        address = self._getaddr('TO:', arg) if arg else None
191
194
        if not address:
192
 
            self.push('501 '+EERR_554+'. Syntax: RCPT TO:<address>')
 
195
            self.push('501 '+EERR_554+' Syntax: RCPT TO:<address>')
 
196
            return
 
197
        # Call rcpttocheck to check if list address has syntax errors
 
198
        if self.rcpttocheck(address) == 'EERR_513':
 
199
            self.push('550 '+EERR_513+' Syntax: list@domain')
193
200
            return
194
201
        # Call rcpttocheck to check if list address is a known address.
195
202
        if self.rcpttocheck(address) == 'EERR_511':
196
 
            self.push('550 '+EERR_511)
197
 
            return
198
 
        # Call rcpttocheck to check if list address has syntax errors
199
 
        if self.rcpttocheck(address) == 'EERR_513':
200
 
            self.push('550 '+EERR_513)
 
203
            self.push('550 '+EERR_511+': '+address)
201
204
            return
202
205
        # get subaddress
203
206
        listname = self.listname(address)
205
208
        # Check if sender is authorised to post to list 
206
209
        if not subaddress in SUBADDRESS_NAMES:
207
210
            if self.listmembercheck(self._mailfrom, address) == 'EERR_572':
208
 
                self.push('550 '+EERR_572)
 
211
                self.push('550 '+EERR_572+': '+address)
209
212
                return   
210
213
        if subaddress in SUBADDRESS_NAMES:
211
214
            if self.listmembercheck(self._mailfrom, listname) == 'EERR_572':
213
216
                    self.push('550 '+EERR_572+', the subaddresses -leave and -unsubscribe can not be used by unauthorised senders')
214
217
                    return
215
218
        self._rcpttos.append(address)
216
 
        self.push('250 '+EERR_200+' Ok') 
 
219
        self.push('250 '+EERR_200+' Ok Recipient address accepted') 
217
220
 
218
221
    def smtp_DATA(self, arg):
219
222
        if not self._rcpttos:
220
 
            self.push('503 '+EERR_551+'. Need RCPT command')
 
223
            self.push('503 '+EERR_551+' Need a valid recipient')
221
224
            return
222
225
        if arg:
223
226
            self.push('501 '+EERR_554+' Syntax: DATA')
228
231
 
229
232
    def smtp_RSET(self, arg):
230
233
        if arg:
231
 
            self.push('501 Syntax: RSET')
 
234
            self.push('501 '+EERR_554+' Syntax: RSET')
232
235
            return
233
236
        # Resets the sender, recipients, and data, but not the greeting
234
237
        self._mailfrom = None
235
238
        self._rcpttos = []
236
239
        self._data = ''
237
240
        self._state = self.COMMAND
238
 
        self.push('250 '+EERR_200+' Reset Ok')
 
241
        self.push('250 '+EERR_200+' Ok Reset')
239
242
 
240
243
    def smtp_NOOP(self, arg):
241
244
        if arg:
242
 
            self.push('501 '+EERR_554+'. Syntax: NOOP')
 
245
            self.push('501 '+EERR_554+' Syntax: NOOP')
243
246
        else:
244
247
            self.push('250 '+EERR_200+' Ok')
245
248
 
277
280
        self._line = []
278
281
        if self._state == self.COMMAND:
279
282
            if not line:
280
 
                self.push('500 '+EERR_551+'. Bad syntax')
 
283
                self.push('500 '+EERR_551+' Bad syntax')
281
284
                return
282
285
            method = None
283
286
            i = line.find(' ')
289
292
                arg = line[i+1:].strip()
290
293
            method = getattr(self, 'smtp_' + command, None)
291
294
            if not method:
292
 
                self.push('500 '+EERR_551+'. Command "%s" not implemented' % command)
 
295
                self.push('500 '+EERR_551+' Command "%s" not implemented' % command)
293
296
                return
294
297
            method(arg)
295
298
            return
296
299
        else:
297
300
            if self._state != self.DATA:
298
 
                self.push('451 '+EERR_450+'. Internal confusion')
 
301
                self.push('451 '+EERR_450+' Internal confusion')
299
302
                return
300
303
            # Remove extraneous carriage returns and de-transparency according
301
304
            # to RFC 821, Section 4.5.2.
409
412
        except Exception, e:
410
413
            elog.exception('LMTP message parsing')
411
414
            config.db.abort()
412
 
            return CRLF.join(['451 '+EERR_450+'. Requested action aborted: error in processing' for to in rcpttos])
 
415
            return CRLF.join(['451 '+EERR_450+' Requested action aborted: error in processing' for to in rcpttos])
413
416
        # RFC 2033 requires us to return a status code for every recipient.
414
417
        status = []
415
418
        # Now for each address in the recipients, parse the address to first