diff --git a/milters/comment_email_milter.py b/milters/comment_email_milter.py new file mode 100644 index 0000000..99ff506 --- /dev/null +++ b/milters/comment_email_milter.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# Milter calls methods of your class at milter events. +# Return REJECT,TEMPFAIL,ACCEPT to short circuit processing for a message. +# You can also add/del recipients, replacebody, add/del headers, etc. + +import base64 +import email +import os +import StringIO +import sys +import time +from socket import AF_INET, AF_INET6 + +import Milter +if True: + from multiprocessing import Process as Thread, Queue +else: + from threading import Thread + from Queue import Queue + +from Milter.utils import parse_addr +from sqlalchemy.exc import SQLAlchemyError + +logq = Queue(maxsize=4) + + +if 'PAGURE_CONFIG' not in os.environ \ + and os.path.exists('/etc/pagure/pagure.cfg'): + print 'Using configuration file `/etc/pagure/pagure.cfg`' + os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg' + +sys.path.insert(0, os.path.expanduser('/srv/progit')) + + +import pagure +import pagure.exceptions +import pagure.lib + + +def get_email_body(emailobj): + ''' Return the body of the email, preferably in text. + ''' + body = None + if emailobj.is_multipart(): + for payload in emailobj.get_payload(): + body = payload.get_payload() + if payload.get_content_type() == 'text/plain': + break + else: + body = emailobj.get_payload() + + enc = emailobj['Content-Transfer-Encoding'] + if enc == 'base64': + body = base64.decodestring(body) + + return body + + + + + +class myMilter(Milter.Base): + + def __init__(self): # A new instance with each new connection. + self.id = Milter.uniqueID() # Integer incremented with each call. + self.fp = None + + def envfrom(self, mailfrom, *str): + self.log("mail from:", mailfrom, *str) + self.fromparms = Milter.dictfromlist(str) + # NOTE: self.fp is only an *internal* copy of message data. You + # must use addheader, chgheader, replacebody to change the message + # on the MTA. + self.fp = StringIO.StringIO() + self.canon_from = '@'.join(parse_addr(mailfrom)) + self.fp.write('From %s %s\n' % (self.canon_from, time.ctime())) + return Milter.CONTINUE + + @Milter.noreply + def connect(self, IPname, family, hostaddr): + self.log("connected") + + return Milter.CONTINUE + + @Milter.noreply + def header(self, name, hval): + self.fp.write("%s: %s\n" % (name, hval)) # add header to buffer + return Milter.CONTINUE + + @Milter.noreply + def eoh(self): + self.fp.write("\n") # terminate headers + return Milter.CONTINUE + + @Milter.noreply + def body(self, chunk): + self.fp.write(chunk) + return Milter.CONTINUE + + ## def envrcpt(self, to, *str): + @Milter.noreply + def envrcpt(self, to, *str): + rcptinfo = to, Milter.dictfromlist(str) + print rcptinfo + + return Milter.CONTINUE + + def eom(self): + self.fp.seek(0) + msg = email.message_from_file(self.fp) + + msg_id = msg['In-Reply-To'] + self.log('msg-ig', msg_id) + self.log('To', msg['to']) + self.log('From', msg['From']) + + for key in ['<', '>']: + if key in msg_id: + msg_id = msg_id.replace(key, '') + + if msg_id and '-ticket-' in msg_id: + return self.handle_ticket_email(msg, msg_id) + elif msg_id and '-pull-request-' in msg_id: + return self.handle_request_email(msg, msg_id) + else: + self.log('Not a pagure ticket or pull-request email, let it go') + return Milter.CONTINUE + + def log(self,*msg): + logq.put((msg, self.id,time.time())) + + def handle_ticket_email(self, emailobj, msg_id): + ''' Add the email as a comment on a ticket. ''' + uid = msg_id.split('-ticket-')[-1].split('@')[0] + parent_id = None + if '-' in uid: + uid, parent_id = uid.rsplit('-', 1) + if '/' in uid: + uid = uid.split('/')[0] + self.log('uid', uid) + self.log('parent_id', parent_id) + + issue = pagure.lib.get_issue_by_uid( + pagure.SESSION, + issue_uid = uid + ) + + if not issue: + self.log('No related ticket found, let it go') + return Milter.CONTINUE + + try: + message = pagure.lib.add_issue_comment( + pagure.SESSION, + issue=issue, + comment=get_email_body(emailobj), + user=emailobj['From'], + ticketfolder=pagure.APP.config['TICKETS_FOLDER'], + ) + pagure.SESSION.commit() + except pagure.exceptions.PagureException, err: + self.log(str(err)) + except SQLAlchemyError, err: # pragma: no cover + pagure.SESSION.rollback() + self.log(str(err)) + + return Milter.ACCEPT + + def handle_request_email(self, emailobj, msg_id): + ''' Add the email as a comment on a request. ''' + msg_id = msg['In-Reply-To'] + uid = msg_id.split('-pull-request-')[-1].split('@')[0] + parent_id = None + if '-' in uid: + uid, parent_id = uid.rsplit('-', 1) + if '/' in uid: + uid = uid.split('/')[0] + self.log('uid', uid) + self.log('parent_id', parent_id) + + request = pagure.lib.get_request_by_uid( + pagure.SESSION, + request_uid = uid + ) + + if not request: + self.log('No related pull-request found, let it go') + return Milter.CONTINUE + + try: + message = pagure.lib.add_pull_request_comment( + pagure.SESSION, + request=request, + comment=get_email_body(emailobj), + user=emailobj['From'], + requestfolder=pagure.APP.config['REQUESTS_FOLDER'], + ) + pagure.SESSION.commit() + except pagure.exceptions.PagureException, err: + self.log(str(err)) + except SQLAlchemyError, err: # pragma: no cover + pagure.SESSION.rollback() + self.log(str(err)) + + return Milter.ACCEPT + + +def background(): + while True: + t = logq.get() + if not t: break + msg,id,ts = t + print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id), + # 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ... + for i in msg: print i, + print + +## === + +def main(): + bt = Thread(target=background) + bt.start() + socketname = "/var/run/paguresock" + timeout = 600 + # Register to have the Milter factory create instances of your class: + Milter.factory = myMilter + flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS + flags += Milter.ADDRCPT + flags += Milter.DELRCPT + Milter.set_flags(flags) # tell Sendmail which features we use + print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S') + sys.stdout.flush() + Milter.runmilter("pythonfilter",socketname,timeout) + logq.put(None) + bt.join() + print "%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S') + +if __name__ == "__main__": + main()