3
# Copyright 2008 Dan Smith <dsmith@danplanet.com>
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.
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.
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/>.
24
from email.mime.multipart import MIMEMultipart
25
from email.mime.base import MIMEBase
26
from email.mime.text import MIMEText
29
from email import MIMEMultipart
30
from email import MIMEBase
31
from email import MIMEText
41
from ui import main_events
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>")
54
if mail.is_multipart():
56
for part in mail.walk():
57
if part.get_content_maintype() == "multipart":
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)
69
body = mail.get_payload(decode=True)
71
if not body and not xml:
72
raise Exception("Unable to find a usable part")
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!"
82
form = formgui.FormFile(tmpfn)
83
recip = form.get_recipient_string()
85
recip, addr = recip.split("%", 1)
88
print "Email from %s: %s" % (sender, subject)
90
recip, addr = rfc822.parseaddr(mail.get("To", "UNKNOWN"))
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)
106
class MailThread(threading.Thread, gobject.GObject):
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)),
117
_signals = __gsignals__
119
def _emit(self, signal, *args):
120
gobject.idle_add(self.emit, signal, *args)
122
def __init__(self, config, host, user, pasw, port=110, ssl=False):
123
threading.Thread.__init__(self)
124
gobject.GObject.__init__(self)
135
self._coerce_call = None
137
def message(self, message):
138
print "[MAIL %s@%s] %s" % (self.username, self.server, message)
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(),
149
form = create_form_from_mail(self.config, mail, ffn)
151
print "Failed to create form from mail: %s" % e
154
if self._coerce_call:
155
print "Coercing to %s" % self._coerce_call
156
form.set_path_dst(self._coerce_call)
160
form.add_path_element("EMAIL")
161
form.add_path_element(self.config.get("user", "callsign"))
164
self._emit("form-received", -999, ffn)
166
def fetch_mails(self):
167
self.message("Querying %s:%i" % (self.server, self.port))
170
server = poplib.POP3_SSL(self.server, self.port)
172
server = poplib.POP3(self.server, self.port)
174
server.user(self.username)
175
server.pass_(self.password)
177
num = len(server.list()[1])
182
self.message("Fetching %i/%i" % (i+1, num))
183
result = server.retr(i+1)
185
message = email.message_from_string("\r\n".join(result[1]))
186
messages.append(message)
193
self.message("One-shot thread starting")
196
if not self.config.getboolean("state", "connected_inet"):
197
result = "Not connected to the Internet"
200
mails = self.fetch_mails()
202
result = "Failed (%s)" % e
206
self.create_form_from_mail(mail)
207
event = main_events.Event(_("Received %i messages") % \
209
self._emit("event", event)
211
result = "Queued %i messages" % len(mails)
212
elif mails is not None:
213
result = "No messages"
215
self.message("Thread ended [ %s ]" % result)
217
self._emit("mail-thread-complete", mails != None, result)
219
class CoercedMailThread(MailThread):
220
def __init__(self, *args):
223
MailThread.__init__(self, *args)
224
self._coerce_call = call
226
class AccountMailThread(MailThread):
227
def __init__(self, config, account):
228
settings = config.get("incoming_email", account)
231
host, user, pasw, poll, ssl, port, action, enb = \
232
settings.split(",", 7)
234
raise Exception("Unable to parse account settings for `%s'" % \
238
_("Form") : self.create_form_from_mail,
239
_("Chat") : self.do_chat_from_mail,
243
self.__action = actions[action]
245
raise Exception("Unsupported action `%s' for %s@%s" % \
246
(action, user, host))
250
port = ssl and 995 or 110
254
self._poll = int(poll)
256
self.event = threading.Event()
257
self.enabled = enb == "True"
259
MailThread.__init__(self, config, host, user, pasw, port, ssl)
261
def do_chat_from_mail(self, mail):
262
if mail.is_multipart():
264
for part in mail.walk():
266
if part.get_content_type() == "text/plain":
267
body = part.get_payload(decode=True)
269
elif part.get_content_type() == "text/html":
270
html = part.get_payload(decode=True)
274
body = mail.get_payload()
276
text = "Message from %s (%s):\r\n%s" % (\
277
mail.get("From", "Unknown Sender"),
278
mail.get("Subject", ""),
281
for port in self.emit("get-station-list").keys():
282
self._emit("user-send-chat", "CQCQCQ", port, text, False)
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)
290
if not self.config.getboolean("state", "connected_inet"):
291
self.message("Not connected")
295
mails = self.fetch_mails()
297
self.message("Failed to retrieve messages: %s" % e)
301
event = main_events.Event(None,
302
"Received %i email(s)" % len(mails))
303
self._emit("event", event)
305
class PeriodicAccountMailThread(AccountMailThread):
307
self.message("Periodic thread starting")
310
AccountMailThread.run(self)
311
self.event.wait(self._poll * 60)
314
self.message("Thread ending")
323
def __validate_access(config, callsign, emailaddr, types):
324
rules = config.options("email_access")
327
rulespec = config.get("email_access", rule)
328
call, access, filter = rulespec.split(",", 2)
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
336
#print "%s -> %s does not match %s,%s,%s" % (callsign, emailaddr,
337
# call, access, filter)
339
print "No match found"
343
def validate_outgoing(config, callsign, emailaddr):
344
return __validate_access(config, callsign, emailaddr, ["Both", "Outgoing"])
346
def validate_incoming(config, callsign, emailaddr):
347
return __validate_access(config, callsign, emailaddr, ["Both", "Incoming"])
349
if __name__ == "__main__":
350
class fakeout(object):
351
form_source_dir = "forms"
354
def reg_form(self, *args):
357
def list_add_form(self, *args, **kwargs):
363
mt = MailThread(None, fakeout())