1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - FetchMessages Action 4 5 @copyright: 2012, 2013 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinSupport import getMetadata, writeHeaders, parseDictEntry 10 from MoinMessage import Message, GPG 11 from MoinMessageSupport import MoinMessageAction, \ 12 get_signing_users, get_recipients 13 from email.mime.text import MIMEText 14 from email.parser import Parser 15 from itertools import islice 16 17 try: 18 from cStringIO import StringIO 19 except ImportError: 20 from StringIO import StringIO 21 22 Dependencies = ['pages'] 23 24 class FetchMessages(MoinMessageAction): 25 26 "A handler for requests accessing messages." 27 28 def handle_message_content(self, content): 29 30 "Handle the given message 'content'." 31 32 request = self.request 33 34 # NOTE: Could employ a more accurate content type. 35 36 if not content.get_content_type() == "text/plain": 37 writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden") 38 request.write("The content does not appear to be a request for messages.") 39 return 40 41 homedir = self.get_homedir() 42 if not homedir: 43 return 44 45 gpg = GPG(homedir) 46 47 # Get keys for signing and encrypting. 48 # The signing key will be this wiki's signing key for the user 49 # requesting the messages. 50 # The encryption key will be the key associated with the user requesting 51 # the messages, found in the recipients mapping. 52 53 recipient = request.user.name 54 55 signing_users = get_signing_users(request) 56 signer = signing_users and signing_users.get(recipient) 57 58 # Get the recipient details. 59 60 recipients = get_recipients(request) 61 if not recipient or not recipients or not recipients.has_key(recipient): 62 writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden") 63 request.write("The recipient does not appear to be registered for message delivery.") 64 return 65 66 recipient_details = recipients[recipient] 67 parameters = parseDictEntry(recipient_details, ("type", "location", "fingerprint",)) 68 69 if not parameters.has_key("fingerprint"): 70 writeHeaders(request, "text/plain", getMetadata(self.page), "403 Forbidden") 71 request.write("The recipient does not appear to be registered for message delivery.") 72 return 73 74 # Obtain commands from the payload, returning a collection of messages. 75 76 commands = content.get_payload(decode=True) 77 78 # Build a container for the responses. 79 80 message = Message() 81 82 # Process each command, using RFC 1939 (POP3) as inspiration. 83 84 for command in commands.split("\n"): 85 command = command.strip() 86 87 # Get the command and arguments. 88 89 command_parts = command.split(None, 1) 90 cmd = command_parts[0] 91 92 # A request to count the messages is returned in a part. 93 94 if cmd == "STAT": 95 result = str(len(self.store)) 96 part = MIMEText(result, "plain") 97 part["Request-Type"] = "STAT" 98 part["Request-Status"] = "OK" 99 message.add_update(part) 100 101 # A request for specific messages returns each message in its own 102 # part. 103 104 elif cmd in ("RETR", "DELE"): 105 106 try: 107 # Either select all. 108 109 if len(command_parts) == 1: 110 count = None 111 112 # Or select a particular number. 113 114 else: 115 count = int(parameters[1]) 116 117 except ValueError: 118 part = MIMEText(command, "plain") 119 part["Request-Type"] = cmd 120 part["Request-Status"] = "ERR" 121 message.add_update(part) 122 123 else: 124 # A request for specific messages returns each message 125 # in its own part within a collection part. 126 127 if cmd == "RETR": 128 container = Message() 129 130 for message_text in islice(iter(self.store), count): 131 message_item = Parser().parse(StringIO(message_text)) 132 container.add_update(message_item) 133 134 # Convert the container to a proper multipart section. 135 136 message.add_update(container.get_payload()) 137 138 # A request to delete messages is performed immediately. 139 140 elif cmd == "DELE": 141 keys = self.store.keys()[:count] 142 keys.sort() 143 144 for key in keys: 145 del self.store[key] 146 147 part = MIMEText(result, "plain") 148 part["Request-Type"] = cmd 149 part["Request-Status"] = "OK" 150 message.add_update(part) 151 152 # Handle invalid commands. 153 154 elif cmd: 155 part = MIMEText(result, "plain") 156 part["Request-Type"] = cmd 157 part["Request-Status"] = "ERR" 158 message.add_update(part) 159 160 # Sign and encrypt the message. 161 162 message = message.get_payload() 163 164 if signer: 165 message = gpg.signMessage(message, signer) 166 167 message = gpg.encryptMessage(message, parameters["fingerprint"]) 168 169 # Write the response. 170 171 writeHeaders(request, "text/plain", getMetadata(self.page)) 172 request.write(message.as_string()) 173 return 1, None 174 175 # Action function. 176 177 def execute(pagename, request): 178 FetchMessages(pagename, request).do_action() # instead of render 179 180 # vim: tabstop=4 expandtab shiftwidth=4