~ubuntu-branches/ubuntu/trusty/d-rats/trusty

« back to all changes in this revision

Viewing changes to d_rats/emailgw.py

  • Committer: Bazaar Package Importer
  • Author(s): Steve Conklin
  • Date: 2011-02-18 18:17:24 UTC
  • Revision ID: james.westby@ubuntu.com-20110218181724-kaxw0ceawnu3wgw6
Tags: upstream-0.3.3~b5
ImportĀ upstreamĀ versionĀ 0.3.3~b5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
#
 
3
# Copyright 2008 Dan Smith <dsmith@danplanet.com>
 
4
#
 
5
# This program is free software: you can redistribute it and/or modify
 
6
# it under the terms of the GNU General Public License as published by
 
7
# the Free Software Foundation, either version 3 of the License, or
 
8
# (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
17
 
 
18
import os
 
19
import threading
 
20
import poplib
 
21
import smtplib
 
22
import email
 
23
try:
 
24
    from email.mime.multipart import MIMEMultipart
 
25
    from email.mime.base import MIMEBase
 
26
    from email.mime.text import MIMEText
 
27
except ImportError:
 
28
    # Python 2.4
 
29
    from email import MIMEMultipart
 
30
    from email import MIMEBase
 
31
    from email import MIMEText 
 
32
 
 
33
import rfc822
 
34
import time
 
35
import platform
 
36
import gobject
 
37
import re
 
38
import random
 
39
 
 
40
import formgui
 
41
from ui import main_events
 
42
import signals
 
43
import utils
 
44
import msgrouting
 
45
 
 
46
 
 
47
def create_form_from_mail(config, mail, tmpfn):
 
48
    subject = mail.get("Subject", "[no subject]")
 
49
    sender = mail.get("From", "Unknown <devnull@nowhere.com>")
 
50
    
 
51
    xml = None
 
52
    body = ""
 
53
 
 
54
    if mail.is_multipart():
 
55
        html = None
 
56
        for part in mail.walk():
 
57
            if part.get_content_maintype() == "multipart":
 
58
                continue
 
59
            elif part.get_content_type() == "d-rats/form_xml":
 
60
                xml = str(part.get_payload())
 
61
                break # A form payload trumps all
 
62
            elif part.get_content_type() == "text/plain":
 
63
                body += part.get_payload(decode=True)
 
64
            elif part.get_content_type() == "text/html":
 
65
                html = part.get_payload(decode=True)
 
66
        if not body:
 
67
            body = html
 
68
    else:
 
69
        body = mail.get_payload(decode=True)
 
70
 
 
71
    if not body and not xml:
 
72
        raise Exception("Unable to find a usable part")
 
73
 
 
74
    messageid = mail.get("Message-ID", time.strftime("%m%d%Y%H%M%S"))
 
75
    if not msgrouting.msg_lock(tmpfn):
 
76
        print "AIEE: Unable to lock incoming email message file!"
 
77
 
 
78
    if xml:
 
79
        f = file(tmpfn, "w")
 
80
        f.write(xml)
 
81
        f.close()
 
82
        form = formgui.FormFile(tmpfn)
 
83
        recip = form.get_recipient_string()
 
84
        if "%" in recip:
 
85
            recip, addr = recip.split("%", 1)
 
86
            recip = recip.upper()
 
87
    else:
 
88
        print "Email from %s: %s" % (sender, subject)
 
89
 
 
90
        recip, addr = rfc822.parseaddr(mail.get("To", "UNKNOWN"))
 
91
 
 
92
        efn = os.path.join(config.form_source_dir(), "email.xml")
 
93
        form = formgui.FormFile(efn)
 
94
        form.set_field_value("_auto_sender", sender)
 
95
        form.set_field_value("recipient", recip)
 
96
        form.set_field_value("subject", "EMAIL: %s" % subject)
 
97
        form.set_field_value("message", utils.filter_to_ascii(body))
 
98
        form.set_path_src(sender.strip())
 
99
        form.set_path_dst(recip.strip())
 
100
        form.set_path_mid(messageid)
 
101
 
 
102
    form.save_to(tmpfn)
 
103
 
 
104
    return form
 
105
 
 
106
class MailThread(threading.Thread, gobject.GObject):
 
107
    __gsignals__ = {
 
108
        "user-send-chat" : signals.USER_SEND_CHAT,
 
109
        "user-send-form" : signals.USER_SEND_FORM,
 
110
        "form-received" : signals.FORM_RECEIVED,
 
111
        "get-station-list" : signals.GET_STATION_LIST,
 
112
        "event" : signals.EVENT,
 
113
        "mail-thread-complete" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
 
114
                                  (gobject.TYPE_BOOLEAN, gobject.TYPE_STRING)),
 
115
        }
 
116
 
 
117
    _signals = __gsignals__
 
118
 
 
119
    def _emit(self, signal, *args):
 
120
        gobject.idle_add(self.emit, signal, *args)
 
121
 
 
122
    def __init__(self, config, host, user, pasw, port=110, ssl=False):
 
123
        threading.Thread.__init__(self)
 
124
        gobject.GObject.__init__(self)
 
125
        self.setDaemon(True)
 
126
 
 
127
        self.username = user
 
128
        self.password = pasw
 
129
        self.server = host
 
130
        self.port = port
 
131
        self.use_ssl = ssl
 
132
 
 
133
        self.config = config
 
134
 
 
135
        self._coerce_call = None
 
136
 
 
137
    def message(self, message):
 
138
        print "[MAIL %s@%s] %s" % (self.username, self.server, message)
 
139
 
 
140
    def create_form_from_mail(self, mail):
 
141
        id = self.config.get("user", "callsign") + \
 
142
            time.strftime("%m%d%Y%H%M%S") + \
 
143
            mail.get("Message-id", str(random.randint(0, 1000)))
 
144
        mid = platform.get_platform().filter_filename(id)
 
145
        ffn = os.path.join(self.config.form_store_dir(),
 
146
                           _("Inbox"),
 
147
                           "%s.xml" % mid)
 
148
        try:
 
149
            form = create_form_from_mail(self.config, mail, ffn)
 
150
        except Exception, e:
 
151
            print "Failed to create form from mail: %s" % e
 
152
            return    
 
153
 
 
154
        if self._coerce_call:
 
155
            print "Coercing to %s" % self._coerce_call
 
156
            form.set_path_dst(self._coerce_call)
 
157
        else:
 
158
            print "Not coercing"
 
159
    
 
160
        form.add_path_element("EMAIL")
 
161
        form.add_path_element(self.config.get("user", "callsign"))
 
162
        form.save_to(ffn)
 
163
    
 
164
        self._emit("form-received", -999, ffn)
 
165
    
 
166
    def fetch_mails(self):
 
167
        self.message("Querying %s:%i" % (self.server, self.port))
 
168
 
 
169
        if self.use_ssl:
 
170
            server = poplib.POP3_SSL(self.server, self.port)
 
171
        else:
 
172
            server = poplib.POP3(self.server, self.port)
 
173
 
 
174
        server.user(self.username)
 
175
        server.pass_(self.password)
 
176
        
 
177
        num = len(server.list()[1])
 
178
 
 
179
        messages = []
 
180
 
 
181
        for i in range(num):
 
182
            self.message("Fetching %i/%i" % (i+1, num))
 
183
            result = server.retr(i+1)
 
184
            server.dele(i+1)
 
185
            message = email.message_from_string("\r\n".join(result[1]))
 
186
            messages.append(message)
 
187
 
 
188
        server.quit()
 
189
 
 
190
        return messages
 
191
 
 
192
    def run(self):
 
193
        self.message("One-shot thread starting")
 
194
        mails = None
 
195
 
 
196
        if not self.config.getboolean("state", "connected_inet"):
 
197
            result = "Not connected to the Internet"
 
198
        else:
 
199
            try:
 
200
                mails = self.fetch_mails()
 
201
            except Exception, e:
 
202
                result = "Failed (%s)" % e
 
203
 
 
204
            if mails:
 
205
                for mail in mails:
 
206
                    self.create_form_from_mail(mail)
 
207
                event = main_events.Event(_("Received %i messages") % \
 
208
                                              len(mails))
 
209
                self._emit("event", event)
 
210
 
 
211
                result = "Queued %i messages" % len(mails)
 
212
            elif mails is not None:
 
213
                result = "No messages"
 
214
 
 
215
        self.message("Thread ended [ %s ]" % result)
 
216
 
 
217
        self._emit("mail-thread-complete", mails != None, result)
 
218
 
 
219
class CoercedMailThread(MailThread):
 
220
    def __init__(self, *args):
 
221
        call = str(args[-1])
 
222
        args = args[:-1]
 
223
        MailThread.__init__(self, *args)
 
224
        self._coerce_call = call
 
225
 
 
226
class AccountMailThread(MailThread):
 
227
    def __init__(self, config, account):
 
228
        settings = config.get("incoming_email", account)
 
229
 
 
230
        try:
 
231
            host, user, pasw, poll, ssl, port, action, enb = \
 
232
                settings.split(",", 7)
 
233
        except ValueError:
 
234
            raise Exception("Unable to parse account settings for `%s'" % \
 
235
                                account)
 
236
 
 
237
        actions = {
 
238
            _("Form") : self.create_form_from_mail,
 
239
            _("Chat") : self.do_chat_from_mail,
 
240
            }
 
241
 
 
242
        try:
 
243
            self.__action = actions[action]
 
244
        except KeyError:
 
245
            raise Exception("Unsupported action `%s' for %s@%s" % \
 
246
                                (action, user, host))
 
247
 
 
248
        ssl = ssl == "True"
 
249
        if not port:
 
250
            port = ssl and 995 or 110
 
251
        else:
 
252
            port = int(port)
 
253
 
 
254
        self._poll = int(poll)
 
255
 
 
256
        self.event = threading.Event()
 
257
        self.enabled = enb == "True"
 
258
 
 
259
        MailThread.__init__(self, config, host, user, pasw, port, ssl)
 
260
 
 
261
    def do_chat_from_mail(self, mail):
 
262
        if mail.is_multipart():
 
263
            body = None
 
264
            for part in mail.walk():
 
265
                html = None
 
266
                if part.get_content_type() == "text/plain":
 
267
                    body = part.get_payload(decode=True)
 
268
                    break
 
269
                elif part.get_content_type() == "text/html":
 
270
                    html = part.get_payload(decode=True)
 
271
            if not body:
 
272
                body = html
 
273
        else:
 
274
            body = mail.get_payload()
 
275
 
 
276
        text = "Message from %s (%s):\r\n%s" % (\
 
277
            mail.get("From", "Unknown Sender"),
 
278
            mail.get("Subject", ""),
 
279
            body)
 
280
            
 
281
        for port in self.emit("get-station-list").keys():
 
282
            self._emit("user-send-chat", "CQCQCQ", port, text, False)
 
283
 
 
284
        event = main_events.Event(None,
 
285
                                  "Mail received from %s and sent via chat" % \
 
286
                                      mail.get("From", "Unknown Sender"))
 
287
        self._emit("event", event)
 
288
 
 
289
    def run(self):
 
290
        if not self.config.getboolean("state", "connected_inet"):
 
291
            self.message("Not connected")
 
292
        else:
 
293
            mails = []
 
294
            try:
 
295
                mails = self.fetch_mails()
 
296
            except Exception, e:
 
297
                self.message("Failed to retrieve messages: %s" % e)
 
298
            for mail in mails:
 
299
                self.__action(mail)
 
300
            if mails:
 
301
                event = main_events.Event(None,
 
302
                                          "Received %i email(s)" % len(mails))
 
303
                self._emit("event", event)
 
304
 
 
305
class PeriodicAccountMailThread(AccountMailThread):
 
306
    def run(self):
 
307
        self.message("Periodic thread starting")
 
308
 
 
309
        while self.enabled:
 
310
            AccountMailThread.run(self)
 
311
            self.event.wait(self._poll * 60)
 
312
            self.event.clear()
 
313
 
 
314
        self.message("Thread ending")
 
315
 
 
316
    def trigger(self):
 
317
        self.event.set()
 
318
 
 
319
    def stop(self):
 
320
        self.enabled = False
 
321
        self.trigger()
 
322
 
 
323
def __validate_access(config, callsign, emailaddr, types):
 
324
    rules = config.options("email_access")
 
325
 
 
326
    for rule in rules:
 
327
        rulespec = config.get("email_access", rule)
 
328
        call, access, filter = rulespec.split(",", 2)
 
329
 
 
330
        if call in [callsign, "*"] and re.search(filter, emailaddr):
 
331
            #print "%s -> %s matches %s,%s,%s" % (callsign, emailaddr,
 
332
            #                                     call, access, filter)
 
333
            #print "Access types allowed: %s" % types
 
334
            return access in types
 
335
        #else:
 
336
            #print "%s -> %s does not match %s,%s,%s" % (callsign, emailaddr,
 
337
            #                                            call, access, filter)
 
338
 
 
339
    print "No match found"
 
340
 
 
341
    return False
 
342
 
 
343
def validate_outgoing(config, callsign, emailaddr):
 
344
    return __validate_access(config, callsign, emailaddr, ["Both", "Outgoing"])
 
345
    
 
346
def validate_incoming(config, callsign, emailaddr):
 
347
    return __validate_access(config, callsign, emailaddr, ["Both", "Incoming"])
 
348
 
 
349
if __name__ == "__main__":
 
350
    class fakeout(object):
 
351
        form_source_dir = "forms"
 
352
        form_store_dir = "."
 
353
 
 
354
        def reg_form(self, *args):
 
355
            pass
 
356
 
 
357
        def list_add_form(self, *args, **kwargs):
 
358
            pass
 
359
 
 
360
        def get_stamp(self):
 
361
            return "FOO"
 
362
 
 
363
    mt = MailThread(None, fakeout())
 
364
    mt.run()