| |
| |
| |
| |
| |
| |
| |
| import base64 |
| import email |
| import hashlib |
| import os |
| import urlparse |
| import StringIO |
| import sys |
| import time |
| from socket import AF_INET, AF_INET6 |
| from multiprocessing import Process as Thread, Queue |
| |
| import Milter |
| import requests |
| |
| from Milter.utils import parse_addr |
| |
| logq = Queue(maxsize=4) |
| |
| |
| if 'PAGURE_CONFIG' not in os.environ \ |
| and os.path.exists('/etc/pagure/pagure.cfg'): |
| os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg' |
| |
| |
| import pagure |
| |
| |
| 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 |
| |
| |
| def clean_item(item): |
| ''' For an item provided as <item> return the content, if there are no |
| <> then return the string. |
| ''' |
| if '<' in item: |
| item = item.split('<')[1] |
| if '>' in item: |
| item = item.split('>')[0] |
| |
| return item |
| |
| |
| class PagureMilter(Milter.Base): |
| |
| def __init__(self): |
| self.id = Milter.uniqueID() |
| self.fp = None |
| |
| def log(self, message): |
| print(message) |
| sys.stdout.flush() |
| |
| def envfrom(self, mailfrom, *str): |
| self.log("mail from: %s - %s" % (mailfrom, str)) |
| self.fromparms = Milter.dictfromlist(str) |
| |
| |
| |
| 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 header(self, name, hval): |
| ''' Headers ''' |
| |
| self.fp.write("%s: %s\n" % (name, hval)) |
| return Milter.CONTINUE |
| |
| @Milter.noreply |
| def eoh(self): |
| ''' End of Headers ''' |
| self.fp.write("\n") |
| return Milter.CONTINUE |
| |
| @Milter.noreply |
| def body(self, chunk): |
| ''' Body ''' |
| self.fp.write(chunk) |
| return Milter.CONTINUE |
| |
| @Milter.noreply |
| def envrcpt(self, to, *str): |
| rcptinfo = to, Milter.dictfromlist(str) |
| print rcptinfo |
| |
| return Milter.CONTINUE |
| |
| def eom(self): |
| ''' End of Message ''' |
| self.fp.seek(0) |
| msg = email.message_from_file(self.fp) |
| |
| msg_id = msg.get('In-Reply-To', None) |
| if msg_id is None: |
| self.log('No In-Reply-To, keep going') |
| return Milter.CONTINUE |
| |
| |
| msg_id = msg_id.split('\n')[0].strip() |
| |
| self.log('msg-ig %s' % msg_id) |
| self.log('To %s' % msg['to']) |
| self.log('From %s' % msg['From']) |
| |
| |
| |
| salt = pagure.APP.config.get('SALT_EMAIL') |
| m = hashlib.sha512('%s%s%s' % (msg_id, salt, clean_item(msg['From']))) |
| tohash= msg['to'].split('@')[0].split('+')[-1] |
| if m.hexdigest() != tohash: |
| self.log('hash: %s' % m.hexdigest()) |
| self.log('tohash: %s' % tohash) |
| self.log('Hash does not correspond to the destination') |
| return Milter.CONTINUE |
| |
| if msg['From'] and msg['From'] == pagure.APP.config.get('FROM_EMAIL'): |
| self.log("Let's not process the email we send") |
| return Milter.CONTINUE |
| |
| msg_id = clean_item(msg_id) |
| |
| if msg_id and '-ticket-' in msg_id: |
| self.log('Processing issue') |
| return self.handle_ticket_email(msg, msg_id) |
| elif msg_id and '-pull-request-' in msg_id: |
| self.log('Processing pull-request') |
| 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 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 %s' % uid) |
| self.log('parent_id %s' % parent_id) |
| |
| data = { |
| 'objid': uid, |
| 'comment': get_email_body(emailobj), |
| 'useremail': clean_item(emailobj['From']), |
| } |
| url = pagure.APP.config.get('APP_URL') |
| |
| if url.endswith('/'): |
| url = url[:-1] |
| url = '%s/pv/ticket/comment/' % url |
| req = requests.put(url, data=data) |
| if req.status_code == 200: |
| self.log('Comment added') |
| return Milter.ACCEPT |
| self.log('Could not add the comment to pagure') |
| return Milter.CONTINUE |
| |
| def handle_request_email(self, emailobj, msg_id): |
| ''' Add the email as a comment on a request. ''' |
| 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 %s' % uid) |
| self.log('parent_id %s' % parent_id) |
| |
| data = { |
| 'objid': uid, |
| 'comment': get_email_body(emailobj), |
| 'useremail': clean_item(emailobj['From']), |
| } |
| url = pagure.APP.config.get('APP_URL') |
| |
| if url.endswith('/'): |
| url = url[:-1] |
| url = '%s/pv/pull-request/comment/' % url |
| req = requests.put(url, data=data) |
| |
| 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), |
| |
| for i in msg: print i, |
| print |
| |
| |
| def main(): |
| bt = Thread(target=background) |
| bt.start() |
| socketname = "/var/run/pagure/paguresock" |
| timeout = 600 |
| |
| Milter.factory = PagureMilter |
| print "%s pagure milter startup" % time.strftime('%Y%b%d %H:%M:%S') |
| sys.stdout.flush() |
| Milter.runmilter("paguremilter", socketname, timeout) |
| logq.put(None) |
| bt.join() |
| print "%s pagure milter shutdown" % time.strftime('%Y%b%d %H:%M:%S') |
| |
| |
| if __name__ == "__main__": |
| main() |