1## @package Milter 2# A thin OO wrapper for the milter module. 3# 4# Clients generally subclass Milter.Base and define callback 5# methods. 6# 7# @author Stuart D. Gathman <stuart@bmsi.com> 8# Copyright 2001,2009 Business Management Systems, Inc. 9# This code is under the GNU General Public License. See COPYING for details. 10 11from __future__ import print_function 12__version__ = '1.0.4' 13 14import os 15import re 16import milter 17try: 18 import thread 19except: 20 # libmilter uses posix threads 21 import _thread as thread 22 23from milter import * 24from functools import wraps 25 26_seq_lock = thread.allocate_lock() 27_seq = 0 28 29def uniqueID(): 30 """Return a unique sequence number (incremented on each call). 31 """ 32 global _seq 33 _seq_lock.acquire() 34 seqno = _seq = _seq + 1 35 _seq_lock.release() 36 return seqno 37 38## @private 39OPTIONAL_CALLBACKS = { 40 'connect':(P_NR_CONN,P_NOCONNECT), 41 'hello':(P_NR_HELO,P_NOHELO), 42 'envfrom':(P_NR_MAIL,P_NOMAIL), 43 'envrcpt':(P_NR_RCPT,P_NORCPT), 44 'data':(P_NR_DATA,P_NODATA), 45 'unknown':(P_NR_UNKN,P_NOUNKNOWN), 46 'eoh':(P_NR_EOH,P_NOEOH), 47 'body':(P_NR_BODY,P_NOBODY), 48 'header':(P_NR_HDR,P_NOHDRS) 49} 50 51MACRO_CALLBACKS = { 52 'connect': M_CONNECT, 53 'hello': M_HELO, 'envfrom': M_ENVFROM, 'envrcpt': M_ENVRCPT, 54 'data': M_DATA, 'eom': M_EOM, 'eoh': M_EOH 55} 56 57## @private 58R = re.compile(r'%+') 59 60## @private 61def decode_mask(bits,names): 62 t = [ (s,getattr(milter,s)) for s in names] 63 nms = [s for s,m in t if bits & m] 64 for s,m in t: bits &= ~m 65 if bits: nms += hex(bits) 66 return nms 67 68## Class decorator to enable optional protocol steps. 69# P_SKIP is enabled by default when supported, but 70# applications may wish to enable P_HDR_LEADSPC 71# to send and receive the leading space of header continuation 72# lines unchanged, and/or P_RCPT_REJ to have recipients 73# detected as invalid by the MTA passed to the envcrpt callback. 74# 75# Applications may want to check whether the protocol is actually 76# supported by the MTA in use. Base._protocol 77# is a bitmask of protocol options negotiated. So, 78# for instance, if <code>self._protocol & Milter.P_RCPT_REJ</code> 79# is true, then that feature was successfully negotiated with the MTA 80# and the application will see recipients the MTA has flagged as invalid. 81# 82# Sample use: 83# <pre> 84# class myMilter(Milter.Base): 85# def envrcpt(self,to,*params): 86# return Milter.CONTINUE 87# myMilter = Milter.enable_protocols(myMilter,Milter.P_RCPT_REJ) 88# </pre> 89# @since 0.9.3 90# @param klass the %milter application class to modify 91# @param mask a bitmask of protocol steps to enable 92# @return the modified %milter class 93def enable_protocols(klass,mask): 94 klass._protocol_mask = klass.protocol_mask() & ~mask 95 return klass 96 97## Milter rejected recipients. A class decorator that calls 98# enable_protocols() with the P_RCPT_REJ flag. By default, the MTA 99# does not pass recipients that it knows are invalid on to the milter. 100# This decorator enables a %milter app to see all recipients if supported 101# by the MTA. Use like this with python-2.6 and later: 102# <pre> 103# @@Milter.rejected_recipients 104# class myMilter(Milter.Base): 105# def envrcpt(self,to,*params): 106# return Milter.CONTINUE 107# </pre> 108# @since 0.9.5 109# @param klass the %milter application class to modify 110# @return the modified %milter class 111def rejected_recipients(klass): 112 return enable_protocols(klass,P_RCPT_REJ) 113 114## Milter leading space on headers. A class decorator that calls 115# enable_protocols() with the P_HDR_LEADSPC flag. By default, 116# header continuation lines are collected and joined before getting 117# sent to a milter. Headers modified or added by the milter are 118# folded by the MTA as necessary according to its own standards. 119# With this flag, header continuation lines are preserved 120# with their newlines and leading space. In addition, header folding 121# done by the milter is preserved as well. 122# Use like this with python-2.6 and later: 123# <pre> 124# @@Milter.header_leading_space 125# class myMilter(Milter.Base): 126# def header(self,hname,value): 127# return Milter.CONTINUE 128# </pre> 129# @since 0.9.5 130# @param klass the %milter application class to modify 131# @return the modified %milter class 132def header_leading_space(klass): 133 return enable_protocols(klass,P_HDR_LEADSPC) 134 135## Function decorator to disable callback methods. 136# If the MTA supports it, tells the MTA not to invoke this callback, 137# increasing efficiency. All the callbacks (except negotiate) 138# are disabled in Milter.Base, and overriding them reenables the 139# callback. An application may need to use @@nocallback when it extends 140# another %milter and wants to disable a callback again. 141# The disabled method should still return Milter.CONTINUE, in case the MTA does 142# not support protocol negotiation, and for when called from a test harness. 143# @since 0.9.2 144def nocallback(func): 145 try: 146 func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1] 147 except KeyError: 148 raise ValueError( 149 '@nocallback applied to non-optional method: '+func.__name__) 150 @wraps(func) 151 def wrapper(self,*args): 152 if func(self,*args) != CONTINUE: 153 raise RuntimeError('%s return code must be CONTINUE with @nocallback' 154 % func.__name__) 155 return CONTINUE 156 return wrapper 157 158## Function decorator to disable callback reply. 159# If the MTA supports it, tells the MTA not to wait for a reply from 160# this callback, and assume CONTINUE. The method should still return 161# CONTINUE in case the MTA does not support protocol negotiation. 162# The decorator arranges to change the return code to NOREPLY 163# when supported by the MTA. 164# @since 0.9.2 165def noreply(func): 166 try: 167 nr_mask = OPTIONAL_CALLBACKS[func.__name__][0] 168 except KeyError: 169 raise ValueError( 170 '@noreply applied to non-optional method: '+func.__name__) 171 @wraps(func) 172 def wrapper(self,*args): 173 rc = func(self,*args) 174 if self._protocol & nr_mask: 175 if rc != CONTINUE: 176 raise RuntimeError('%s return code must be CONTINUE with @noreply' 177 % func.__name__) 178 return NOREPLY 179 return rc 180 wrapper.milter_protocol = nr_mask 181 return wrapper 182 183## Function decorator to set macros used in a callback. 184# By default, the MTA sends all macros defined for a callback. 185# If some or all of these are unused, the bandwidth can be saved 186# by listing the ones that are used. 187# @since 1.0.2 188def symlist(*syms): 189 if len(syms) > 5: 190 raise ValueError('@symlist limited to 5 macros by MTA: '+func.__name__) 191 def setsyms(func): 192 if func.__name__ not in MACRO_CALLBACKS: 193 raise ValueError('@symlist applied to non-symlist method: '+func.__name__) 194 func._symlist = syms 195 return func 196 return setsyms 197 198## Disabled action exception. 199# set_flags() can tell the MTA that this application will not use certain 200# features (such as CHGFROM). This can also be negotiated for each 201# connection in the negotiate callback. If the application then calls 202# the feature anyway via an instance method, this exception is 203# thrown. 204# @since 0.9.2 205class DisabledAction(RuntimeError): 206 pass 207 208## A do "nothing" Milter base class representing an SMTP connection. 209# 210# Python milters should derive from this class 211# unless they are using the low level milter module directly. 212# 213# Most of the methods are either "actions" or "callbacks". Callbacks 214# are invoked by the MTA at certain points in the SMTP protocol. For 215# instance when the HELO command is seen, the MTA calls the helo 216# callback before returning a response code. All callbacks must 217# return one of these constants: CONTINUE, TEMPFAIL, REJECT, ACCEPT, 218# DISCARD, SKIP. The NOREPLY response is supplied automatically by 219# the @@noreply decorator if negotiation with the MTA is successful. 220# @@noreply and @@nocallback methods should return CONTINUE for two reasons: 221# the MTA may not support negotiation, and the class may be running in a test 222# harness. 223# 224# Optional callbacks are disabled with the @@nocallback decorator, and 225# automatically reenabled when overridden. Disabled callbacks should 226# still return CONTINUE for testing and MTAs that do not support 227# negotiation. 228 229# Each SMTP connection to the MTA calls the factory method you provide to 230# create an instance derived from this class. This is typically the 231# constructor for a class derived from Base. The _setctx() method attaches 232# the instance to the low level milter.milterContext object. When the SMTP 233# connection terminates, the close callback is called, the low level connection 234# object is destroyed, and this normally causes instances of this class to be 235# garbage collected as well. The close() method should release any global 236# resources held by instances. 237# @since 0.9.2 238class Base(object): 239 "The core class interface to the %milter module." 240 241 ## Attach this Milter to the low level milter.milterContext object. 242 def _setctx(self,ctx): 243 ## The low level @ref milter.milterContext object. 244 self._ctx = ctx 245 ## A bitmask of actions this connection has negotiated to use. 246 # By default, all actions are enabled. High throughput milters 247 # may want to disable unused actions to increase efficiency. 248 # Some optional actions may be disabled by calling milter.set_flags(), or 249 # by overriding the negotiate callback. The bits include: 250 # <code>ADDHDRS,CHGBODY,MODBODY,ADDRCPT,ADDRCPT_PAR,DELRCPT 251 # CHGHDRS,QUARANTINE,CHGFROM,SETSYMLIST</code>. 252 # The <code>Milter.CURR_ACTS</code> bitmask is all actions 253 # known when the milter module was compiled. 254 # Application code can also inspect this field to determine 255 # which actions are available. This is especially useful in 256 # generic library code designed to work in multiple milters. 257 # @since 0.9.2 258 # 259 self._actions = CURR_ACTS # all actions enabled by default 260 ## A bitmask of protocol options this connection has negotiated. 261 # An application may inspect this 262 # variable to determine which protocol steps are supported. Options 263 # of interest to applications: the SKIP result code is allowed 264 # only if the P_SKIP bit is set, rejected recipients are passed to the 265 # %milter application only if the P_RCPT_REJ bit is set, and 266 # header values are sent and received with leading spaces (in the 267 # continuation lines) intact if the P_HDR_LEADSPC bit is set (so 268 # that the application can customize indenting). 269 # 270 # The P_N* bits should be negotiated via the @@noreply and @@nocallback 271 # method decorators, and P_RCPT_REJ, P_HDR_LEADSPC should 272 # be enabled using the enable_protocols class decorator. 273 # 274 # The bits include: <code> 275 # P_RCPT_REJ P_NR_CONN P_NR_HELO P_NR_MAIL P_NR_RCPT P_NR_DATA P_NR_UNKN 276 # P_NR_EOH P_NR_BODY P_NR_HDR P_NOCONNECT P_NOHELO P_NOMAIL P_NORCPT 277 # P_NODATA P_NOUNKNOWN P_NOEOH P_NOBODY P_NOHDRS P_HDR_LEADSPC P_SKIP 278 # </code> (all under the Milter namespace). 279 # @since 0.9.2 280 self._protocol = 0 # no protocol options by default 281 if ctx: 282 ctx.setpriv(self) 283 284 ## Defined by subclasses to write log messages. 285 def log(self,*msg): pass 286 ## Called for each connection to the MTA. Called by the 287 # <a href="milter_api/xxfi_connect.html"> 288 # xxfi_connect</a> callback. 289 # The <code>hostname</code> provided by the local MTA is either 290 # the PTR name or the IP in the form "[1.2.3.4]" if no PTR is available. 291 # The format of hostaddr depends on the socket family: 292 # <dl> 293 # <dt><code>socket.AF_INET</code> 294 # <dd>A tuple of (IP as string in dotted quad form, integer port) 295 # <dt><code>socket.AF_INET6</code> 296 # <dd>A tuple of (IP as a string in standard representation, 297 # integer port, integer flow info, integer scope id) 298 # <dt><code>socket.AF_UNIX</code> 299 # <dd>A string with the socketname 300 # </dl> 301 # To vary behavior based on what port the client connected to, 302 # for example skipping blacklist checks for port 587 (which must 303 # be authenticated), use @link #getsymval getsymval('{daemon_port}') @endlink. 304 # The <code>{daemon_port}</code> macro must be enabled in sendmail.cf 305 # <pre> 306 # O Milter.macros.connect=j, _, {daemon_name}, {daemon_port}, {if_name}, {if_addr} 307 # </pre> 308 # or sendmail.mc 309 # <pre> 310 # define(`confMILTER_MACROS_CONNECT', ``j, _, {daemon_name}, {daemon_port}, {if_name}, {if_addr}'')dnl 311 # </pre> 312 # @param hostname the PTR name or bracketed IP of the SMTP client 313 # @param family <code>socket.AF_INET</code>, <code>socket.AF_INET6</code>, 314 # or <code>socket.AF_UNIX</code> 315 # @param hostaddr a tuple or string with peer IP or socketname 316 @nocallback 317 def connect(self,hostname,family,hostaddr): return CONTINUE 318 ## Called when the SMTP client says HELO. 319 # Returning REJECT prevents progress until a valid HELO is provided; 320 # this almost always results in terminating the connection. 321 @nocallback 322 def hello(self,hostname): return CONTINUE 323 ## Called when the SMTP client says MAIL FROM. Called by the 324 # <a href="milter_api/xxfi_envfrom.html"> 325 # xxfi_envfrom</a> callback. 326 # Returning REJECT rejects the message, but not the connection. 327 # The sender is the "envelope" from as defined by 328 # <a href="http://tools.ietf.org/html/rfc5321">RFC 5321</a>. 329 # For the From: header (author) defined in 330 # <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>, 331 # see @link #header the header callback @endlink. 332 @nocallback 333 def envfrom(self,f,*str): return CONTINUE 334 ## Called when the SMTP client says RCPT TO. Called by the 335 # <a href="milter_api/xxfi_envrcpt.html"> 336 # xxfi_envrcpt</a> callback. 337 # Returning REJECT rejects the current recipient, not the entire message. 338 # The recipient is the "envelope" recipient as defined by 339 # <a href="http://tools.ietf.org/html/rfc5321">RFC 5321</a>. 340 # For recipients defined in 341 # <a href="http://tools.ietf.org/html/rfc5322">RFC 5322</a>, 342 # for example To: or Cc:, see @link #header the header callback @endlink. 343 @nocallback 344 def envrcpt(self,to,*str): return CONTINUE 345 ## Called when the SMTP client says DATA. 346 # Returning REJECT rejects the message without wasting bandwidth 347 # on the unwanted message. 348 # @since 0.9.2 349 @nocallback 350 def data(self): return CONTINUE 351 ## Called for each header field in the message body. 352 @nocallback 353 def header(self,field,value): return CONTINUE 354 ## Called at the blank line that terminates the header fields. 355 @nocallback 356 def eoh(self): return CONTINUE 357 ## Called to supply the body of the message to the Milter by chunks. 358 # @param blk a block of message bytes 359 @nocallback 360 def body(self,blk): return CONTINUE 361 ## Called when the SMTP client issues an unknown command. 362 # @param cmd the unknown command 363 # @since 0.9.2 364 @nocallback 365 def unknown(self,cmd): return CONTINUE 366 ## Called at the end of the message body. 367 # Most of the message manipulation actions can only take place from 368 # the eom callback. 369 def eom(self): return CONTINUE 370 ## Called when the connection is abnormally terminated. 371 # The close callback is still called also. 372 def abort(self): return CONTINUE 373 ## Called when the connection is closed. 374 def close(self): return CONTINUE 375 376 ## Return mask of SMFIP_N* protocol option bits to clear for this class 377 # The @@nocallback and @@noreply decorators set the 378 # <code>milter_protocol</code> function attribute to the protocol mask bit to 379 # pass to libmilter, causing that callback or its reply to be skipped. 380 # Overriding a method creates a new function object, so that 381 # <code>milter_protocol</code> defaults to 0. 382 # Libmilter passes the protocol bits that the current MTA knows 383 # how to skip. We clear the ones we don't want to skip. 384 # The negation is somewhat mind bending, but it is simple. 385 # @since 0.9.2 386 @classmethod 387 def protocol_mask(klass): 388 try: 389 return klass._protocol_mask 390 except AttributeError: 391 p = P_RCPT_REJ | P_HDR_LEADSPC # turn these new features off by default 392 for func,(nr,nc) in OPTIONAL_CALLBACKS.items(): 393 func = getattr(klass,func) 394 ca = getattr(func,'milter_protocol',0) 395 #print(func,hex(nr),hex(nc),hex(ca)) 396 p |= (nr|nc) & ~ca 397 klass._protocol_mask = p 398 return p 399 400 ## Negotiate milter protocol options. Called by the 401 # <a href="milter_api/xxfi_negotiate.html"> 402 # xffi_negotiate</a> callback. This is an advanced callback, 403 # do not override unless you know what you are doing. Most 404 # negotiation can be done simply by using the supplied 405 # class and function decorators. 406 # Options are passed as 407 # a list of 4 32-bit ints which can be modified and are passed 408 # back to libmilter on return. 409 # Default negotiation sets P_NO* and P_NR* for callbacks 410 # marked @@nocallback and @@noreply respectively, leaves all 411 # actions enabled, and enables Milter.SKIP. The @@enable_protocols 412 # class decorator can customize which protocol steps are implemented. 413 # @param opts a modifiable list of 4 ints with negotiated options 414 # @since 0.9.2 415 def negotiate(self,opts): 416 try: 417 self._actions,p,f1,f2 = opts 418 for func,stage in MACRO_CALLBACKS.items(): 419 func = getattr(self,func) 420 syms = getattr(func,'_symlist',None) 421 if syms is not None: 422 self.setsymlist(stage,*syms) 423 opts[1] = self._protocol = p & ~self.protocol_mask() 424 opts[2] = 0 425 opts[3] = 0 426 #self.log("Negotiated:",opts) 427 except Exception as x: 428 # don't change anything if something went wrong 429 return ALL_OPTS 430 return CONTINUE 431 432 # Milter methods which can be invoked from most callbacks 433 434 ## Return the value of an MTA macro. Sendmail macro names 435 # are either single chars (e.g. "j") or multiple chars enclosed 436 # in braces (e.g. "{auth_type}"). Macro names are MTA dependent. 437 # See <a href="milter_api/smfi_getsymval.html"> 438 # smfi_getsymval</a> for default sendmail macros. 439 # @param sym the macro name 440 def getsymval(self,sym): 441 return self._ctx.getsymval(sym) 442 443 ## Set the SMTP reply code and message. 444 # If the MTA does not support setmlreply, then only the 445 # first msg line is used. Any '%%' in a message line 446 # must be doubled, or libmilter will silently ignore the setreply. 447 # Beginning with 0.9.6, we test for that case and throw ValueError to avoid 448 # head scratching. What will <i>really</i> irritate you, however, 449 # is that if you carefully double any '%%', your message will be 450 # sent - but with the '%%' still doubled! 451 # See <a href="milter_api/smfi_setreply.html"> 452 # smfi_setreply</a> for more information. 453 # @param rcode The three-digit (RFC 821/2821) SMTP reply code as a string. 454 # rcode cannot be None, and <b>must be a valid 4XX or 5XX reply code</b>. 455 # @param xcode The extended (RFC 1893/2034) reply code. If xcode is None, 456 # no extended code is used. Otherwise, xcode must conform to RFC 1893/2034. 457 # @param msg The text part of the SMTP reply. If msg is None, 458 # an empty message is used. 459 # @param ml Optional additional message lines. 460 def setreply(self,rcode,xcode=None,msg=None,*ml): 461 for m in (msg,)+ml: 462 if 1 in [len(s)&1 for s in R.findall(m)]: 463 raise ValueError("'%' must be doubled: "+m) 464 return self._ctx.setreply(rcode,xcode,msg,*ml) 465 466 ## Tell the MTA which macro names will be used. 467 # This information can reduce the size of messages received from sendmail, 468 # and hence could reduce bandwidth between sendmail and your milter where 469 # that is a factor. The <code>Milter.SETSYMLIST</code> action flag must be 470 # set. The protocol stages are M_CONNECT, M_HELO, M_ENVFROM, M_ENVRCPT, 471 # M_DATA, M_EOM, M_EOH. 472 # 473 # May only be called from negotiate callback. Hence, this is an advanced 474 # feature. Use the @@symlist function decorator to conviently set 475 # the macros used by a callback. 476 # @since 0.9.8, previous version was misspelled! 477 # @param stage the protocol stage to set to macro list for, 478 # one of the M_* constants defined in Milter 479 # @param macros space separated and/or lists of strings 480 def setsymlist(self,stage,*macros): 481 if not self._actions & SETSYMLIST: raise DisabledAction("SETSYMLIST") 482 if len(macros) > 5: 483 raise ValueError('setsymlist limited to 5 macros by MTA') 484 a = [] 485 for m in macros: 486 try: 487 m = m.encode('utf8') 488 except: pass 489 try: 490 m = m.split(b' ') 491 a += m 492 except: pass 493 return self._ctx.setsymlist(stage,b' '.join(a)) 494 495 # Milter methods which can only be called from eom callback. 496 497 ## Add a mail header field. 498 # Calls <a href="milter_api/smfi_addheader.html"> 499 # smfi_addheader</a>. 500 # The <code>Milter.ADDHDRS</code> action flag must be set. 501 # 502 # May be called from eom callback only. 503 # @param field the header field name 504 # @param value the header field value 505 # @param idx header field index from the top of the message to insert at 506 # @throws DisabledAction if ADDHDRS is not enabled 507 def addheader(self,field,value,idx=-1): 508 if not self._actions & ADDHDRS: raise DisabledAction("ADDHDRS") 509 return self._ctx.addheader(field,value,idx) 510 511 ## Change the value of a mail header field. 512 # Calls <a href="milter_api/smfi_chgheader.html"> 513 # smfi_chgheader</a>. 514 # The <code>Milter.CHGHDRS</code> action flag must be set. 515 # 516 # May be called from eom callback only. 517 # @param field the name of the field to change 518 # @param idx index of the field to change when there are multiple instances 519 # @param value the new value of the field 520 # @throws DisabledAction if CHGHDRS is not enabled 521 def chgheader(self,field,idx,value): 522 if not self._actions & CHGHDRS: raise DisabledAction("CHGHDRS") 523 return self._ctx.chgheader(field,idx,value) 524 525 ## Add a recipient to the message. 526 # Calls <a href="milter_api/smfi_addrcpt.html"> 527 # smfi_addrcpt</a>. 528 # If no corresponding mail header is added, this is like a Bcc. 529 # The syntax of the recipient is the same as used in the SMTP 530 # RCPT TO command (and as delivered to the envrcpt callback), for example 531 # "self.addrcpt('<foo@example.com>')". 532 # The <code>Milter.ADDRCPT</code> action flag must be set. 533 # If the optional <code>params</code> argument is used, then 534 # the <code>Milter.ADDRCPT_PAR</code> action flag must be set. 535 # 536 # May be called from eom callback only. 537 # @param rcpt the message recipient 538 # @param params an optional list of ESMTP parameters 539 # @throws DisabledAction if ADDRCPT or ADDRCPT_PAR is not enabled 540 def addrcpt(self,rcpt,params=None): 541 if not self._actions & ADDRCPT: raise DisabledAction("ADDRCPT") 542 if params and not self._actions & ADDRCPT_PAR: 543 raise DisabledAction("ADDRCPT_PAR") 544 return self._ctx.addrcpt(rcpt,params) 545 ## Delete a recipient from the message. 546 # Calls <a href="milter_api/smfi_delrcpt.html"> 547 # smfi_delrcpt</a>. 548 # The recipient should match one passed to the envrcpt callback. 549 # The <code>Milter.DELRCPT</code> action flag must be set. 550 # 551 # May be called from eom callback only. 552 # @param rcpt the message recipient to delete 553 # @throws DisabledAction if DELRCPT is not enabled 554 def delrcpt(self,rcpt): 555 if not self._actions & DELRCPT: raise DisabledAction("DELRCPT") 556 return self._ctx.delrcpt(rcpt) 557 558 ## Replace the message body. 559 # Calls <a href="milter_api/smfi_replacebody.html"> 560 # smfi_replacebody</a>. 561 # The entire message body must be replaced. 562 # Call repeatedly with blocks of data until the entire body is transferred. 563 # The <code>Milter.MODBODY</code> action flag must be set. 564 # 565 # May be called from eom callback only. 566 # @param body a chunk of body data 567 # @throws DisabledAction if MODBODY is not enabled 568 def replacebody(self,body): 569 if not self._actions & MODBODY: raise DisabledAction("MODBODY") 570 return self._ctx.replacebody(body) 571 572 ## Change the SMTP envelope sender address. 573 # Calls <a href="milter_api/smfi_chgfrom.html"> 574 # smfi_chgfrom</a>. 575 # The syntax of the sender is that same as used in the SMTP 576 # MAIL FROM command (and as delivered to the envfrom callback), 577 # for example <code>self.chgfrom('<bar@example.com>')</code>. 578 # The <code>Milter.CHGFROM</code> action flag must be set. 579 # 580 # May be called from eom callback only. 581 # @since 0.9.1 582 # @param sender the new sender address 583 # @param params an optional list of ESMTP parameters 584 # @throws DisabledAction if CHGFROM is not enabled 585 def chgfrom(self,sender,params=None): 586 if not self._actions & CHGFROM: raise DisabledAction("CHGFROM") 587 return self._ctx.chgfrom(sender,params) 588 589 ## Quarantine the message. 590 # Calls <a href="milter_api/smfi_quarantine.html"> 591 # smfi_quarantine</a>. 592 # When quarantined, a message goes into the mailq as if to be delivered, 593 # but delivery is deferred until the message is unquarantined. 594 # The <code>Milter.QUARANTINE</code> action flag must be set. 595 # 596 # May be called from eom callback only. 597 # @param reason a string describing the reason for quarantine 598 # @throws DisabledAction if QUARANTINE is not enabled 599 def quarantine(self,reason): 600 if not self._actions & QUARANTINE: raise DisabledAction("QUARANTINE") 601 return self._ctx.quarantine(reason) 602 603 ## Tell the MTA to wait a bit longer. 604 # Calls <a href="milter_api/smfi_progress.html"> 605 # smfi_progress</a>. 606 # Resets timeouts in the MTA that detect a "hung" milter. 607 def progress(self): 608 return self._ctx.progress() 609 610## A logging but otherwise do nothing Milter base class. 611# This is included for compatibility with previous versions of pymilter. 612# The logging callbacks are marked @@noreply. 613class Milter(Base): 614 "A simple class interface to the milter module." 615 616 ## Provide simple logging to sys.stdout 617 def log(self,*msg): 618 print('Milter:',end=None) 619 for i in msg: print(i,end=None) 620 print() 621 622 @noreply 623 def connect(self,hostname,family,hostaddr): 624 "Called for each connection to sendmail." 625 self.log("connect from %s at %s" % (hostname,hostaddr)) 626 return CONTINUE 627 628 @noreply 629 def hello(self,hostname): 630 "Called after the HELO command." 631 self.log("hello from %s" % hostname) 632 return CONTINUE 633 634 @noreply 635 def envfrom(self,f,*str): 636 """Called to begin each message. 637 f -> string message sender 638 str -> tuple additional ESMTP parameters 639 """ 640 self.log("mail from",f,str) 641 return CONTINUE 642 643 @noreply 644 def envrcpt(self,to,*str): 645 "Called for each message recipient." 646 self.log("rcpt to",to,str) 647 return CONTINUE 648 649 @noreply 650 def header(self,field,value): 651 "Called for each message header." 652 self.log("%s: %s" % (field,value)) 653 return CONTINUE 654 655 @noreply 656 def eoh(self): 657 "Called after all headers are processed." 658 self.log("eoh") 659 return CONTINUE 660 661 def eom(self): 662 "Called at the end of message." 663 self.log("eom") 664 return CONTINUE 665 666 def abort(self): 667 "Called if the connection is terminated abnormally." 668 self.log("abort") 669 return CONTINUE 670 671 def close(self): 672 "Called at the end of connection, even if aborted." 673 self.log("close") 674 return CONTINUE 675 676## The milter connection factory 677# This factory method is called for each connection to create the 678# python object that tracks the connection. It should return 679# an object derived from Milter.Base. 680# 681# Note that since python is dynamic, this variable can be changed while 682# the milter is running: for instance, to a new subclass based on a 683# change in configuration. 684factory = Milter 685 686## @private 687# @brief Connect context to connection instance and return enabled callbacks. 688def negotiate_callback(ctx,opts): 689 m = factory() 690 m._setctx(ctx) 691 return m.negotiate(opts) 692 693## @private 694# @brief Connect context if needed and invoke connect method. 695def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN): 696 m = ctx.getpriv() 697 if not m: 698 # If not already created (because the current MTA doesn't support 699 # xmfi_negotiate), create the connection object. 700 m = factory() 701 m._setctx(ctx) 702 return m.connect(hostname,family,hostaddr) 703 704## @private 705# @brief Disconnect milterContext and call close method. 706def close_callback(ctx): 707 m = ctx.getpriv() 708 if not m: return CONTINUE 709 try: 710 rc = m.close() 711 finally: 712 m._setctx(None) # release milterContext 713 return rc 714 715## Convert ESMTP parameters with values to a keyword dictionary. 716# @deprecated You probably want Milter.param2dict instead. 717def dictfromlist(args): 718 "Convert ESMTP parms with values to keyword dictionary." 719 kw = {} 720 for s in args: 721 pos = s.find('=') 722 if pos > 0: 723 kw[s[:pos].upper()] = s[pos+1:] 724 return kw 725 726## Convert ESMTP parm list to keyword dictionary. 727# Params with no value are set to None in the dictionary. 728# @since 0.9.3 729# @param str list of param strings of the form "NAME" or "NAME=VALUE" 730# @return a dictionary of ESMTP param names and values 731def param2dict(str): 732 "Convert ESMTP parm list to keyword dictionary." 733 pairs = [x.split('=',1) for x in str] 734 for e in pairs: 735 if len(e) < 2: e.append(None) 736 return dict([(k.upper(),v) for k,v in pairs]) 737 738def envcallback(c,args): 739 """Call function c with ESMTP parms converted to keyword parameters. 740 Can be used in the envfrom and/or envrcpt callbacks to process 741 ESMTP parameters as python keyword parameters.""" 742 kw = {} 743 pargs = [args[0]] 744 for s in args[1:]: 745 pos = s.find('=') 746 if pos > 0: 747 kw[s[:pos].upper()] = s[pos+1:] 748 else: 749 pargs.append(s) 750 return c(*pargs,**kw) 751 752## Run the %milter. 753# @param name the name of the %milter known to the MTA 754# @param socketname the socket to be passed to milter.setconn() 755# @param timeout the time in secs the MTA should wait for a response before 756# considering this %milter dead 757def runmilter(name,socketname,timeout = 0,rmsock=True): 758 759 # The default flags set include everything 760 # milter.set_flags(milter.ADDHDRS) 761 milter.set_connect_callback(connect_callback) 762 milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host)) 763 # For envfrom and envrcpt, we would like to convert ESMTP parms to keyword 764 # parms, but then all existing users would have to include **kw to accept 765 # arbitrary keywords without crashing. We do provide envcallback and 766 # dictfromlist to make parsing the ESMTP args convenient. 767 milter.set_envfrom_callback(lambda ctx,*str: ctx.getpriv().envfrom(*str)) 768 milter.set_envrcpt_callback(lambda ctx,*str: ctx.getpriv().envrcpt(*str)) 769 milter.set_header_callback(lambda ctx,fld,val: ctx.getpriv().header(fld,val)) 770 milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh()) 771 milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk)) 772 milter.set_eom_callback(lambda ctx: ctx.getpriv().eom()) 773 milter.set_abort_callback(lambda ctx: ctx.getpriv().abort()) 774 milter.set_close_callback(close_callback) 775 776 milter.setconn(socketname) 777 if timeout > 0: milter.settimeout(timeout) 778 # disable negotiate callback if runtime version < (1,0,1) 779 ncb = negotiate_callback 780 if milter.getversion() < (1,0,1): 781 ncb = None 782 # The name *must* match the X line in sendmail.cf (supposedly) 783 milter.register(name, 784 data=lambda ctx: ctx.getpriv().data(), 785 unknown=lambda ctx,cmd: ctx.getpriv().unknown(cmd), 786 negotiate=ncb 787 ) 788 789 # We remove the socket here by default on the assumption that you will be 790 # starting this filter before sendmail. If sendmail is not running and the 791 # socket already exists, libmilter will throw a warning. If sendmail is 792 # running, this is still safe if there are no messages currently being 793 # processed. It's safer to shutdown sendmail, kill the filter process, 794 # restart the filter, and then restart sendmail. 795 milter.opensocket(rmsock) 796 start_seq = _seq 797 try: 798 milter.main() 799 except milter.error: 800 if start_seq == _seq: raise # couldn't start 801 # milter has been running for a while, but now it can't start new threads 802 raise milter.error("out of thread resources") 803 804__all__ = globals().copy() 805for priv in ('os','milter','thread','factory','_seq','_seq_lock','__version__'): 806 del __all__[priv] 807__all__ = __all__.keys() 808 809## @example milter-template.py 810## @example milter-nomix.py 811# 812