Postfix's Transport Encryption under Control of the User
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

views.py 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import subprocess
  2. import re
  3. from django.shortcuts import render
  4. from django.utils import timezone
  5. from core.models import TLSNotification, TLSLogEntry
  6. def mailaction(request):
  7. """
  8. This view is called by the sender of an email that was deferred by Postfix
  9. because of missing TLS support of the recipients mail server.
  10. It redirects or deletes the email.
  11. The view expects URL parameters:
  12. # /?id=06DA1A40B9B&action=redirect
  13. or
  14. # /?id=06DA1A40B9B&action=delete
  15. """
  16. ######################################################
  17. # Get parameters from url
  18. try:
  19. queue_id = request.GET.get('queue_id', '')
  20. action = request.GET.get('action', '')
  21. except:
  22. queue_id = ""
  23. action = ""
  24. # Show the frontpage if no parameters are given in URL
  25. if queue_id == "" or action == "":
  26. return render(request,
  27. 'core/frontpage.html',
  28. {"reason": "no parameters"})
  29. ######################################################
  30. # Raise an error if the mail with the given queue id is not
  31. # existent in the queue on the postfix server
  32. p = subprocess.Popen(['sudo', 'postcat', '-qh', queue_id],
  33. stdin=subprocess.PIPE,
  34. stdout=subprocess.PIPE,
  35. stderr=subprocess.STDOUT)
  36. output = str(p.stdout.read(), "utf-8")
  37. if "No such file or directory" in output:
  38. return render(request,
  39. 'core/error.html',
  40. {"queue_id": queue_id,
  41. "output": output, })
  42. ######################################################
  43. # SEND MAIL UNENCRYPTED
  44. if action == "redirect":
  45. ##########################################################################
  46. # Put mail in hold queue so that Postfix does not start another
  47. # attempt to deliver it.
  48. p = subprocess.Popen(['sudo', 'postsuper', '-h', queue_id],
  49. stdin=subprocess.PIPE,
  50. stdout=subprocess.PIPE,
  51. stderr=subprocess.STDOUT)
  52. output = str(p.stdout.read(), "utf-8")
  53. ##########################################################################
  54. # Get envelope information
  55. # We need:
  56. # - the sender: see below, if we do not pass the sender to sendmail, it sets
  57. # "root@mail.domain.com" as the envelope sender. This is
  58. # not what we want.
  59. # - the recipients: we do want to send the mail not to all recipients mentioned
  60. # in the header. The mail was already sent to most of the
  61. # recipients. We just want so send the mail to those recipients
  62. # who did not already get the email since there were errors so
  63. # the mail was not delivered. These are the recipient lines in
  64. # the envelope!
  65. # Get the envelope for queue_id and grep the line with "recipient:" and "sender:"
  66. # TODO: Use Python, not egrep!
  67. p1 = subprocess.Popen(['sudo', 'postcat', '-qe', queue_id], stdout=subprocess.PIPE)
  68. p2 = subprocess.Popen(['egrep', '^recipient:|sender:'], stdin=p1.stdout, stdout=subprocess.PIPE)
  69. p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
  70. envelope = p2.communicate()[0]
  71. # extract recipient and sender addresses
  72. recipients = ""
  73. for line in envelope.decode("utf-8").split('\n'):
  74. if line is not "": # there is an empty line at the end after splitting, so make sure it is ignored
  75. if "recipient:" in line:
  76. recipients += re.search('recipient:.(.*)', line).group(1) + " "
  77. elif "sender:" in line:
  78. envelope_sender = re.search('sender:.(.*)', line).group(1)
  79. ##########################################################################
  80. # redirect mail to second mail server instance
  81. #
  82. # Information about the used sendmail options:
  83. #
  84. # -t option extracts recipients from message header. This is *not* what we want!
  85. # The mail is already sent to recipients with no errors. We just want to send
  86. # the mail to the recipients that had errors. These are mentioned in the
  87. # "recipient" lines of the envelope. So we collected them in the variable
  88. # "recipients" and pass them to sendmail.
  89. #
  90. # -C send mail to specified postfix instance (second postfix instance with opp. TLS)
  91. #
  92. # -f option sets the envelope sender.
  93. #
  94. # If we do not set the envelope sender, we get this:
  95. #
  96. # Received: from [XXX.XXX.XXX.XXX] (helo=mail.domain.com)
  97. # by mail2.domain.com with esmtp (Exim 4.84)
  98. # (envelope-from <root@mail.domain.com>)
  99. # id 1a9pFz-0006zY-0r
  100. # for user@domain.com; Fri, 18 Dec 2015 08:15:15 +0100
  101. #
  102. # but we want to have this:
  103. #
  104. # Received: from [XXX.XXX.XXX.XXX] (helo=mail.domain.com)
  105. # by mail2.domain.com with esmtp (Exim 4.84)
  106. # (envelope-from <mailbox@suenkler.info>)
  107. # id 1a9pD5-00014n-PX
  108. # for user@domain.com; Fri, 18 Dec 2015 08:12:16 +0100
  109. #
  110. # TODO: Make sure, the sendmail command is correct!
  111. # Depending on the environment it could be necessary to set -F (sender full name)
  112. # to an empty string.
  113. p1 = subprocess.Popen(['sudo', 'postcat', '-qbh', queue_id],
  114. stdout=subprocess.PIPE)
  115. p2 = subprocess.Popen(['sendmail', '-C', '/etc/postfix-out/', '-f', envelope_sender, recipients],
  116. stdin=p1.stdout,
  117. stdout=subprocess.PIPE)
  118. p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
  119. output = p2.communicate()[0].decode("utf-8")
  120. ##########################################################################
  121. # now delete mail from queue
  122. p = subprocess.Popen(['sudo', 'postsuper', '-d', queue_id],
  123. stdin=subprocess.PIPE,
  124. stdout=subprocess.PIPE,
  125. stderr=subprocess.STDOUT)
  126. output += str(p.stdout.read(), "utf-8")
  127. # Create log entry in database
  128. q = TLSLogEntry(queue_id=queue_id,
  129. sender=envelope_sender,
  130. action=action,
  131. recipients=recipients,
  132. date=timezone.now())
  133. q.save()
  134. ######################################################
  135. # DELETE MAIL
  136. elif action == "delete":
  137. ##########################################################################
  138. # Delete mail from queue
  139. p = subprocess.Popen(['sudo', 'postsuper', '-d', queue_id],
  140. stdin=subprocess.PIPE,
  141. stdout=subprocess.PIPE,
  142. stderr=subprocess.STDOUT)
  143. output = str(p.stdout.read(), "utf-8")
  144. ################################################################################
  145. # The mail is now deleted or redirected, so we can delete the database entry
  146. # of the last notification
  147. try:
  148. notification = TLSNotification.objects.get(queue_id=queue_id)
  149. except:
  150. notification = ""
  151. if notification:
  152. notification.delete()
  153. #######################################################
  154. # Return Success Page
  155. return render(request,
  156. 'core/mailaction.html',
  157. {"queue_id": queue_id,
  158. "action": action,
  159. "output": output, })