/src/mailman/rules/suspicious.py

https://gitlab.com/noc0lour/mailman · Python · 92 lines · 59 code · 3 blank · 30 comment · 12 complexity · 5ca0a605a9c02d9224310e6eed74cb15 MD5 · raw file

  1. # Copyright (C) 2007-2016 by the Free Software Foundation, Inc.
  2. #
  3. # This file is part of GNU Mailman.
  4. #
  5. # GNU Mailman is free software: you can redistribute it and/or modify it under
  6. # the terms of the GNU General Public License as published by the Free
  7. # Software Foundation, either version 3 of the License, or (at your option)
  8. # any later version.
  9. #
  10. # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
  11. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  13. # more details.
  14. #
  15. # You should have received a copy of the GNU General Public License along with
  16. # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
  17. """The historical 'suspicious header' rule."""
  18. import re
  19. import logging
  20. from mailman import public
  21. from mailman.core.i18n import _
  22. from mailman.interfaces.rules import IRule
  23. from zope.interface import implementer
  24. log = logging.getLogger('mailman.error')
  25. @public
  26. @implementer(IRule)
  27. class SuspiciousHeader:
  28. """The historical 'suspicious header' rule."""
  29. name = 'suspicious-header'
  30. description = _('Catch messages with suspicious headers.')
  31. record = True
  32. def check(self, mlist, msg, msgdata):
  33. """See `IRule`."""
  34. return (mlist.bounce_matching_headers and
  35. has_matching_bounce_header(mlist, msg))
  36. def _parse_matching_header_opt(mlist):
  37. """Return a list of triples [(field name, regex, line), ...]."""
  38. # - Blank lines and lines with '#' as first char are skipped.
  39. # - Leading whitespace in the matchexp is trimmed - you can defeat
  40. # that by, eg, containing it in gratuitous square brackets.
  41. all = []
  42. for line in mlist.bounce_matching_headers.splitlines():
  43. line = line.strip()
  44. # Skip blank lines and lines *starting* with a '#'.
  45. if not line or line.startswith('#'):
  46. continue
  47. i = line.find(':')
  48. if i < 0:
  49. # This didn't look like a header line. BAW: should do a
  50. # better job of informing the list admin.
  51. log.error('bad bounce_matching_header line: %s\n%s',
  52. mlist.display_name, line)
  53. else:
  54. header = line[:i]
  55. value = line[i+1:].lstrip()
  56. try:
  57. cre = re.compile(value, re.IGNORECASE)
  58. except re.error as error:
  59. # The regexp was malformed. BAW: should do a better
  60. # job of informing the list admin.
  61. log.error("""\
  62. bad regexp in bounce_matching_header line: %s
  63. \n%s (cause: %s)""", mlist.display_name, value, error)
  64. else:
  65. all.append((header, cre, line))
  66. return all
  67. def has_matching_bounce_header(mlist, msg):
  68. """Does the message have a matching bounce header?
  69. :param mlist: The mailing list the message is destined for.
  70. :param msg: The email message object.
  71. :return: True if a header field matches a regexp in the
  72. bounce_matching_header mailing list variable.
  73. """
  74. for header, cre, line in _parse_matching_header_opt(mlist):
  75. for value in msg.get_all(header, []):
  76. if cre.search(value):
  77. return True
  78. return False