aboutsummaryrefslogtreecommitdiffstats
path: root/src/acit/imapplugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/acit/imapplugin.py')
-rw-r--r--src/acit/imapplugin.py147
1 files changed, 129 insertions, 18 deletions
diff --git a/src/acit/imapplugin.py b/src/acit/imapplugin.py
index 2ceb0a1..6a85855 100644
--- a/src/acit/imapplugin.py
+++ b/src/acit/imapplugin.py
@@ -8,7 +8,7 @@ import cherrypy
import re
-from imap_tools import MailBox, MailMessage
+from imap_tools import MailBox, MailMessage, MailMessageFlags
from mariadb import Connection, Cursor
@@ -78,6 +78,8 @@ class ImapPlugin(plugins.SimplePlugin):
self.smtp.domain=self.emaildomain
+ self.msgids_sorted_not_indexed=[]
+
self.mlog("Starting mailbox pool. This can take a while.")
self.mbpool.open()
pool=self.mbpool.get_pool_size()
@@ -125,7 +127,10 @@ class ImapPlugin(plugins.SimplePlugin):
mb.folder.set(self.get_bug_folder(mb,tracker,bugid))
for msg in mb.fetch():
if "message-id" in msg.headers:
- cur.execute("REPLACE INTO msgindex VALUES (?,?,?)",(tracker,bugid,msg.headers["message-id"][:255]))
+ msgid=msg.headers["message-id"]
+ if msgid in self.msgids_sorted_not_indexed:
+ self.msgids_sorted_not_indexed.remove(msgid)
+ cur.execute("REPLACE INTO msgindex VALUES (?,?,?)",(tracker,bugid,msgid[:255]))
except Exception as e:
self.mlog("Error while indexing mailbox of %s/%d: %s"%(tracker,bugid,e))
@@ -141,6 +146,7 @@ class ImapPlugin(plugins.SimplePlugin):
def format_emailaddr(self,project=None,bugid=None,subject=None,headers={}):
from urllib.parse import quote as quote
email=self.addr_format.format(proj=project, bug=bugid)
+ email=email.replace("None#","#")
email=email.replace("#None",'')
email=email.replace("+None",'')
if subject:
@@ -214,6 +220,8 @@ class ImapPlugin(plugins.SimplePlugin):
try:
target=self.handle_email(mailbox,msg)
+ if "message-id" in msg.headers: self.msgids_sorted_not_indexed.append(msg.headers["message-id"])
+
if target:
proj,bug=target
refreshable.setdefault(proj,[]).append(bug)
@@ -224,7 +232,7 @@ class ImapPlugin(plugins.SimplePlugin):
self.mlog("Message-ID:",msg.headers["message-id"] if "message-id" in msg.headers else "None")
self.mlog("Moving mail to INBOX/Errors.")
if "message-id" in msg.headers:
- self.mail_error(msg=msg,notice="There was an error processing your email with message id:\n "+msg.headers["message-id"]+"\nPlease contact the instance administrator.")
+ self.mail_error(msg=msg,notice="There was an error processing your email.\nPlease contact the instance administrator.")
self.move_errored_mail(mailbox,msg)
# block: update all webpages that received new mail
@@ -250,8 +258,25 @@ class ImapPlugin(plugins.SimplePlugin):
def handle_email(self,mailbox:MailBox,msg:MailMessage):
+ if "message-id" in msg.headers:
+ msgid=msg.headers["message-id"]
+ skip=False
+ if msgid in self.msgids_sorted_not_indexed:
+ skip=True
+ else:
+ with self.dbpool.get_connection() as conn, conn.cursor() as cur:
+ cur.execute("SELECT messageid FROM msgindex WHERE messageid=? LIMIT 1",(msgid))
+ if cur.fetchone():
+ skip=True
+ if skip:
+ self.mlog("An email with messageid %s already exists, skipping"%msgid)
+ mailbox.flag([msg.uid],MailMessageFlags.SEEN,value=False) # mark as unread
+ mailbox.move([msg.uid],self.ensurefolder(mailbox,"INBOX","duplicates"))
+ return
+
self.mlog("Processing email with subject '%s'"%msg.subject)
+
if "x-acit-delete-when-sender" in msg.headers and "sender" in msg.headers:
if msg.headers["x-acit-delete-when-sender"]==msg.headers["sender"]:
self.mlog("Header requested deletion, deleting email")
@@ -265,13 +290,21 @@ class ImapPlugin(plugins.SimplePlugin):
return
+ proj=None
+ bug=None
for addr in msg.to + msg.cc + msg.bcc + msg.reply_to:
if re.fullmatch(self.addr_regex,addr):
- proj,bug=self.stripInfoFromMailAddr(addr)
- break
- else:
- proj=None
- bug=None
+ newproj,newbug=self.stripInfoFromMailAddr(addr)
+ if newproj and not proj or proj==newproj: # if we can set proj, set it.
+ proj=newproj
+ if newbug and not bug: # if we can set a bugid, set it too.
+ # the reason this block is below the project block, is to ensure we don't set a bugid with an unrelated proj
+ bug=newbug
+
+ if newproj.startswith("$token$="):
+ proj=newproj
+ break # ensure no further things get set, we wanna use the token
+
if msg.subject.startswith("SUBSCRIBE"):
from_=self.format_emailaddr(project=proj,bugid=bug)
@@ -312,13 +345,13 @@ class ImapPlugin(plugins.SimplePlugin):
return
# block: handle secure email which uses $token$ as projectname
- if proj.startswith('$token$='):
+ if proj and proj.startswith('$token$='):
token=proj.removeprefix('$token$=')
self.mlog("This email is sent to a secure token, resolving (matching against %s)."%token)
with self.dbpool.get_connection() as conn, conn.cursor() as cur:
- cur.execute("SELECT email,tracker,bugid FROM tokens WHERE token=? AND usedin IS NULL",(token,))
+ cur.execute("SELECT email,tracker,bugid,usedin FROM tokens WHERE token=? AND usedin IS NULL",(token,))
data=cur.fetchone()
- if data and data[0]==msg.from_:
+ if data and data[0]==msg.from_: # sender matches, or token was used before
self.mlog("Resolved to",data)
proj=data[1]
bug=data[2]
@@ -328,6 +361,7 @@ class ImapPlugin(plugins.SimplePlugin):
self.mlog("Error. IDK.",traceback=True)
self.mail_error("Error processing commands:\n "+repr(e)+"\nPlease contact a site admin.")
+ # set token used state
cur.execute("UPDATE tokens SET usedin=? WHERE token=?",(msg.headers["message-id"] if "message-id" else "nomessageid"),token)
conn.commit()
else:
@@ -477,20 +511,97 @@ class ImapPlugin(plugins.SimplePlugin):
return mailbox.move(msg.uid,target)
- def fwd_to_list(self,msg:MailMessage,bug:Bug,replyto:MailMessage=None):
- tracker=self.site.gettracker(bug.tracker)
- bcc=bug.subscribers + tracker.subscribers
+ def email_postprocessor_fix_acit_a_thousand_times(self,email,realacitfrom=None):
+ if not realacitfrom:
+ realacitfrom=self.format_emailaddr()
+
+ acit_re_added=False
+ for header in "to", "cc":
+ if not header in email:
+ continue
+ cur=email[header]
+ new=""
+ while True:
+ match=re.search(self.addr_regex+"(, )?",cur)
+ if match:
+ self.mlog("Removing",match.group(),"from",header)
+ new+=match.string[:match.start()]
+ cur=match.string[match.end():]
+ if not acit_re_added:
+ self.mlog("Readding",realacitfrom,"instead")
+ new+=realacitfrom
+ acit_re_added=True
+ self.mlog(header,"= ",new,"<<",cur)
+ else:
+ break
+
+ if new:
+ del email[header]
+ email[header]=new
+
+ if not ( realacitfrom in email["to"] or realacitfrom in email.get("cc","") ):
+ cc=( email["cc"]+", " if email["cc"] else "" )
+ del email["cc"]
+ email["Cc"]=cc+realacitfrom
+
+
+ def fwd_to_list(self,msg:MailMessage,bug:Bug,replyto:MailMessage=None,bcc:list=None):
+ import random, string
+ if not bcc:
+ tracker=self.site.gettracker(bug.tracker)
+ bcc=bug.subscribers + tracker.subscribers
self.mlog("Bcc:",bcc)
+ securereplytos={}
+ with self.dbpool.get_connection() as conn, conn.cursor() as cur:
+ for email in bcc:
+ try:
+ cur.execute("SELECT email FROM permissiontable WHERE email=? AND (tracker=? OR tracker IS NULL) AND (bugid=? OR bugid IS NULL) LIMIT 1",(email,bug.tracker,bug.bugid))
+ if cur.fetchone():
+ token=''.join(( random.choice(string.ascii_letters) for i in range(40)))
+ cur.execute("UPDATE tokens SET usedin='@@UNUSED@@' WHERE email=? AND tracker=? AND bugid=? AND USEDIN IS NULL",(email,bug.tracker,bug.bugid))
+
+ cur.execute("INSERT INTO tokens (email,token,tracker,bugid) VALUES (?,?,?,?)",(msg.from_,token,bug.tracker,bug.bugid))
+ securereplytos[email]=token
+ self.mlog("Token",token,"generated for",email)
+
+ except Exception:
+ self.mlog("Error generating a secure email token for",email,traceback=True)
+
+ conn.commit()
+
+ for email in securereplytos.keys():
+ self.mlog("Removing",email,"from bcc")
+ while email in bcc:
+ bcc.remove(email)
+
+ self.mlog("Bcc 2:",bcc)
+
from_=self.format_emailaddr(bug.tracker,bug.bugid)
+ kwargs={
+ "tosend":msg,
+ "replyto":replyto,
+ }
+
self.smtp.sendmail(
+ from_=from_,
+ bcc=bcc,
+ postprocessor=lambda msg: self.email_postprocessor_fix_acit_a_thousand_times(msg, from_),
+ smtp_to=bcc,
+ **kwargs
+ )
+
+ for email,token in securereplytos.items():
+ self.mlog("Sending extra email to %s bc of token"%email)
+ from_=self.format_emailaddr(project='$token$='+token)
+ self.smtp.sendmail(
+ to=email,
from_=from_,
- bcc=bcc,
- tosend=msg,
- replyto=replyto,
- extraheaders={"Reply-To":from_}
+ postprocessor=lambda msg: self.email_postprocessor_fix_acit_a_thousand_times(msg, from_),
+ smtp_to=email,
+ **kwargs
)
def secure_generate_token(self,mailbox:MailBox,msg:MailMessage,proj:str,bugid:int):