HEX
Server: Apache/2.2.34 (Unix) mod_fastcgi/mod_fastcgi-SNAP-0910052141
System: Linux Kou-Etsu-Dou 4.4.59+ #25556 SMP PREEMPT Thu Mar 4 18:03:46 CST 2021 x86_64
User: hosam (1026)
PHP: 7.2.29
Disabled: NONE
Upload Files
File: /volume1/@appstore/MailPlus-Server/bin/vacation
#!/usr/bin/env python3
# --*-- coding:utf8 --*--

import binascii
import syslog
import sys
from email.errors import HeaderParseError

sys.path.append("/var/packages/MailPlus-Server/target/scripts/common/")

from addr_util import *
from send_mail import send_mail

debug = False
#reply once in a week
reply_interval = 86400 * 7

def parseArgs():
    import optparse

    parser = optparse.OptionParser(usage='%prog [options] username')
    parser.add_option('-b', '--begin_time', dest='begin_time',
                    type=str, help='start time of auto-reply')
    parser.add_option('-e', '--end_time', dest='end_time',
                    type=str, help='end time of auto-reply')
    parser.add_option('-m', '--no_match', dest='no_match',
                    action='store_true', default=False,
                    help='do not match receivere in To: CC: header')
    parser.add_option('-d', '--debug', dest='debug',
                    action='store_true', default=False,
                    help='output debug message')
    options, args = parser.parse_args()
    if len(args) != 1:
        parser.print_usage()
        exit(0)
    return options, args

def parseTime(time_str):
    import time
    try:
        return time.strptime(time_str, '%Y/%m/%d-%H:%M:%S')
    except:
        pass
    try:
        return time.strptime(time_str, '%Y/%m/%d')
    except:
        if debug:
            writeLog('Failed to pares time string [{0}]'.format(time_str))
        return None

def checkInTimeRange(begin_time, end_time):
    import time
    now_time = time.localtime()
    if begin_time is not None:
        begin_time = parseTime(begin_time)
        if begin_time is not None and begin_time > now_time:
            if debug:
                writeLog('Time now is before start time')
            exit(0)
    if end_time is not None:
        end_time = parseTime(end_time)
        if end_time is not None and end_time < now_time:
            if debug:
                writeLog('Time now is after end time')
            exit(0)

def parseMail():
    import sys
    import email.parser as parser
    from email.utils import getaddresses

    try:
        mail_parser = parser.FeedParser()
        for line in sys.stdin:
            if len(line.strip()) == 0:
                break
            mail_parser.feed(line)
        mail = mail_parser.close()
        froms = mail.get_all('from', [])
        tos = mail.get_all('to', [])
        ccs = mail.get_all('cc', [])
        all_senders = getaddresses(froms)
        all_recipients = getaddresses(tos + ccs)
        subject = convertSubject(mail['subject'])
    except Exception as e:
        if debug:
            writeLog('Failed to parse message, error [{0}]'.format(e))
        return [], [], ''

    return all_senders, all_recipients, subject

def getMaildirPath(fullUsername):
    args = ['/var/packages/MailPlus-Server/target/bin/syno_mailserver_backend', '--getMailDir' , fullUsername]
    return execCommand(args)[0]

def mailconfGet(key):
    args = ['/var/packages/MailPlus-Server/target/bin/syno_mailserver_backend', '--getConfKeyVal' , key]
    return execCommand(args)[0]

def getLastReplyTime(db_path, sender, matched_addr):
    import os
    import _gdbm as gdbm

    last_reply_time = 0
    db = None
    try:
        if os.path.isfile(db_path):
            try:
                db = gdbm.open(db_path, 'ru', 0o644)
            except gdbm.error as e:
                if str(e) == 'Malformed database file header':
                    os.unlink(db_path)
                    db = gdbm.open(db_path, 'ru', 0o644)
                else:
                    raise
            key = sender + '/' + matched_addr
            if key in db:
                try:
                    last_reply_time = int(db[key])
                except:
                    last_reply_time = 0
    except Exception as e:
        if debug:
            writeLog('Failed to get record [{0}] in db [{1}], error [{2}]'.format(sender, db_path, e))
    finally:
        if db is not None:
            db.close()
    return last_reply_time

def setRelpyTime(db_path, sender, matched_addr, time):
    import _gdbm as gdbm
    db = None
    try:
        db = gdbm.open(db_path, 'cu', 0o644)
        key = sender + '/' + matched_addr
        db[key] = '{0}'.format(time)
    except Exception as e:
        if debug:
            writeLog('Failed to set record [{0}] in db [{1}], error [{2}]'.format(sender, db_path, e))
    finally:
        if db is not None:
            db.close()

def checkMatch(username, sender, all_recipients):
    own_addresses = getUserAddress(getFullUsername(username))

    punycode_sender = converToPunycodeAddr(sender)
    if punycode_sender in own_addresses:
        if debug:
            writeLog('We do not do auto-reply to ourself')
        exit(0)
    for recipient in all_recipients:
        punycode_addr = converToPunycodeAddr(recipient[1])
        if punycode_addr.lower() in own_addresses:
            #We use punycode address to do auto-reply
            return True, punycode_addr
    main_domain = mailconfGet('smtp_main_domain')
    return False, '{0}@{1}'.format(username, main_domain.encode('idna').decode('utf8'))

def getDomain(addr):
    at_idx = addr.rfind('@')
    if at_idx == -1:
        return addr
    return addr[at_idx + 1:]

def findReplyFile(maildir_path, sender_addr):
    import os
    possible_files = list()
    if len(maildir_path) == 0:
        if debug:
            writeLog('Failed to get maildir path')
        exit(0)
    if isAsciiString(getDomain(sender_addr)):
        punycode_address = sender_addr
        eai_address = converToEaiAddr(sender_addr)
        possible_files.append('.{0}.msg'.format(punycode_address))
        possible_files.append('.{0}.msg'.format(eai_address))
        possible_files.append('.{0}.msg'.format(getDomain(punycode_address)))
        possible_files.append('.{0}.msg'.format(getDomain(eai_address)))
    else:
        punycode_address = converToPunycodeAddr(sender_addr)
        eai_address = sender_addr
        possible_files.append('.{0}.msg'.format(eai_address))
        possible_files.append('.{0}.msg'.format(punycode_address))
        possible_files.append('.{0}.msg'.format(getDomain(eai_address)))
        possible_files.append('.{0}.msg'.format(getDomain(punycode_address)))

    possible_files.append('.vacation.msg')

    for one_file in possible_files:
        file_path = os.path.join(maildir_path, one_file)
        if os.path.isfile(file_path):
            return file_path

    if debug:
        writeLog('Theew is no auto-reply message files')
    exit(0)

def convertSubject(subject_header):
    import re
    from email.header import decode_header
    # convert consecutive space\tab in between the subject item to only one item
    internal_blank_re = re.compile('(?<!^)\s+|\s+(?!$)')
    all_subjects = list()
    if subject_header is None:
        return ''
    try:
        decoded_subjects = decode_header(subject_header)
    except:
        return subject_header.replace('\n', ' ').replace('\r', '')

    for subject_item in decoded_subjects:
        if subject_item[1] is not None:
            all_subjects.append(subject_item[0].decode(subject_item[1]))
        else:
            all_subjects.append(internal_blank_re.sub(' ', subject_item[0]))
    return ''.join(all_subjects)

def sendReply(sender, matched_addr, ori_subject, reply_file, db_path):
    import time

    msg_content = ''
    subject = ''
    found_subject = False

    #covert sender to eai address unconditionally
    if isAsciiString(getDomain(sender)):
        eai_sender = converToEaiAddr(sender)
    else:
        eai_sender = sender

    with open(reply_file, 'r') as in_f:
        for line in in_f:
            if not found_subject:
                if line.startswith('Subject:'):
                    subject = line[len('Subject:'):].rstrip('\n')
                    subject = subject.replace('$SUBJECT', ori_subject).replace('$FROM', eai_sender)
                    found_subject = True
                continue
            line = line.replace('$SUBJECT', ori_subject).replace('$FROM', eai_sender)
            msg_content += line

    try:
        send_mail(
            subject=subject,
            msg=msg_content,
            sender=matched_addr,
            recipients=sender
        )
        setRelpyTime(db_path, sender, matched_addr, int(time.time()))
    except:
        if debug:
            writeLog('Failed to send reply message')

def main():
    import os
    import time

    global debug
    options, args = parseArgs()
    debug = options.debug
    no_match = options.no_match
    matched = False
    matched_addr = ''
    username = args[0]

    if debug:
        syslog.openlog(os.path.basename(__file__), syslog.LOG_PID, syslog.LOG_MAIL)

    checkInTimeRange(options.begin_time, options.end_time)

    all_senders, all_recipients, subject = parseMail()
    if len(all_senders) == 0:
        if debug:
            writeLog('There is no From: header in message')
        exit(0)

    #we only reply to the first address in From: header
    sender = all_senders[0][1]

    maildir_path = getMaildirPath(getFullUsername(username))
    if not maildir_path.startswith('/var/spool/mail'):
        if debug:
            writeLog('The maildir of user [{0}] does not exist'.format(username))
        exit(0)

    db_path = os.path.join(maildir_path, '.vacation.db')
    matched, matched_addr = checkMatch(username, sender, all_recipients)
    if no_match:
        matched = True
    if not matched:
        if debug:
            writeLog('User is not listed in To: or CC: headers')
        exit(0)

    last_reply_time = getLastReplyTime(db_path, sender, matched_addr)
    if time.time() < last_reply_time + reply_interval:
        if debug:
            writeLog('You do not need to reply')
        exit(0)

    reply_file = findReplyFile(maildir_path, sender)
    sendReply(sender, matched_addr, subject, reply_file, db_path)

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        import sys
        sys.stderr.write('"{0}" fails, error [{1}]\n'.format(' '.join(sys.argv), e))
        if debug:
            writeLog('"{0}" fails, error [{1}]'.format(' '.join(sys.argv), e))
        exit(1)