2020-02-23 09:58:24 +01:00
|
|
|
#!/usr/bin/python3
|
2019-05-12 20:49:56 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2020-10-26 23:08:03 +01:00
|
|
|
import MySQLdb as dbapi
|
2019-05-12 20:49:56 +02:00
|
|
|
import argparse
|
2020-10-26 23:08:03 +01:00
|
|
|
import configparser
|
2019-05-12 20:49:56 +02:00
|
|
|
import imaplib
|
2020-10-27 22:16:53 +01:00
|
|
|
import os
|
2020-02-23 09:58:24 +01:00
|
|
|
import re
|
2020-10-27 21:53:16 +01:00
|
|
|
import subprocess
|
2020-10-26 23:08:03 +01:00
|
|
|
import sys
|
2019-05-12 20:49:56 +02:00
|
|
|
|
|
|
|
opts = {}
|
|
|
|
INBOX = 'INBOX'
|
2020-10-27 21:53:16 +01:00
|
|
|
ST_RUNNING = 1
|
2019-05-12 20:49:56 +02:00
|
|
|
|
2023-05-01 20:58:42 +02:00
|
|
|
imaplib._MAXLINE = 10000000
|
2019-05-12 20:49:56 +02:00
|
|
|
|
2022-12-01 18:21:11 +01:00
|
|
|
def generate_auth_string(user, token):
|
|
|
|
auth_string = f"user={user}\1auth=Bearer {token}\1\1"
|
|
|
|
return auth_string
|
|
|
|
|
|
|
|
|
2020-10-26 23:08:03 +01:00
|
|
|
def read_options(filename="", opts={}):
|
|
|
|
s = "[piler]\n" + open(filename, 'r').read()
|
|
|
|
config = configparser.ConfigParser()
|
|
|
|
config.read_string(s)
|
|
|
|
|
|
|
|
if config.has_option('piler', 'mysqlhost'):
|
|
|
|
opts['dbhost'] = config.get('piler', 'mysqlhost')
|
|
|
|
else:
|
|
|
|
opts['dbhost'] = 'localhost'
|
|
|
|
|
|
|
|
opts['username'] = config.get('piler', 'mysqluser')
|
|
|
|
opts['password'] = config.get('piler', 'mysqlpwd')
|
|
|
|
opts['database'] = config.get('piler', 'mysqldb')
|
|
|
|
|
|
|
|
|
2019-05-12 20:49:56 +02:00
|
|
|
def read_folder_list(conn):
|
|
|
|
result = []
|
|
|
|
|
|
|
|
rc, folders = conn.list()
|
2020-02-23 09:58:24 +01:00
|
|
|
if opts['verbose']:
|
|
|
|
print("Folders:", folders)
|
|
|
|
|
2019-05-12 20:49:56 +02:00
|
|
|
for folder in folders:
|
2020-02-23 09:58:24 +01:00
|
|
|
if opts['verbose']:
|
|
|
|
print("Got folder", folder)
|
|
|
|
|
|
|
|
if isinstance(folder, type(b'')):
|
|
|
|
folder = folder.decode('utf-8')
|
|
|
|
elif isinstance(folder, type(())):
|
2022-12-01 18:21:11 +01:00
|
|
|
folder = re.sub(r'\{\d+\}$', '',
|
|
|
|
folder[0].decode('utf-8')) + folder[1].decode('utf-8')
|
2020-02-23 09:58:24 +01:00
|
|
|
|
|
|
|
# The regex should match ' "/" ' and ' "." '
|
|
|
|
if folder:
|
2021-01-27 09:05:19 +01:00
|
|
|
f = re.split(r' \"[\/\.\\]+\" ', folder)
|
2020-02-23 09:58:24 +01:00
|
|
|
result.append(f[1])
|
2019-05-12 20:49:56 +02:00
|
|
|
|
|
|
|
return [x for x in result if x not in opts['skip_folders']]
|
|
|
|
|
|
|
|
|
|
|
|
def process_folder(conn, folder):
|
2021-01-27 09:05:19 +01:00
|
|
|
# Space in the folder name must be escaped
|
|
|
|
folder = re.sub(r' ', '\\ ', folder)
|
|
|
|
|
2020-10-26 23:08:03 +01:00
|
|
|
if opts['verbose']:
|
|
|
|
print("Processing {}".format(folder))
|
2020-02-23 09:58:24 +01:00
|
|
|
|
2022-07-05 15:17:15 +02:00
|
|
|
try:
|
|
|
|
rc, data = conn.select(folder)
|
|
|
|
except:
|
|
|
|
print("Error processing folder {}".format(folder))
|
|
|
|
return
|
|
|
|
|
|
|
|
if rc != "OK":
|
2022-12-01 18:21:11 +01:00
|
|
|
print("Error processing folder {}, rc={}, response={}".format(folder,
|
|
|
|
rc, data))
|
2022-07-05 15:17:15 +02:00
|
|
|
return
|
|
|
|
|
2019-05-12 20:49:56 +02:00
|
|
|
n = int(data[0])
|
2020-10-26 23:08:03 +01:00
|
|
|
if opts['verbose']:
|
|
|
|
print("Folder {} has {} messages".format(folder, n))
|
2019-05-12 20:49:56 +02:00
|
|
|
|
|
|
|
if n > 0:
|
2020-10-27 21:53:16 +01:00
|
|
|
if opts['id']:
|
|
|
|
cursor = opts['db'].cursor()
|
|
|
|
data = (ST_RUNNING, n, opts['id'])
|
2022-12-01 18:21:11 +01:00
|
|
|
cursor.execute("UPDATE import SET status=%s, total=total+%s WHERE id=%s",
|
|
|
|
data)
|
2020-10-27 21:53:16 +01:00
|
|
|
opts['db'].commit()
|
|
|
|
|
2020-11-01 08:51:59 +01:00
|
|
|
rc, data = conn.search(None, opts['search'])
|
2019-05-12 20:49:56 +02:00
|
|
|
for num in data[0].split():
|
|
|
|
rc, data = conn.fetch(num, '(RFC822)')
|
2020-02-23 09:58:24 +01:00
|
|
|
if opts['verbose']:
|
|
|
|
print(rc, num)
|
2021-01-27 09:05:19 +01:00
|
|
|
if isinstance(data[0], tuple):
|
|
|
|
opts['counter'] += 1
|
|
|
|
with open("{}.eml".format(opts['counter']), "wb") as f:
|
|
|
|
f.write(data[0][1])
|
2023-05-01 20:58:42 +02:00
|
|
|
if opts['remove']:
|
|
|
|
conn.store(num, '+FLAGS', '\\Deleted')
|
|
|
|
|
|
|
|
if opts['remove']:
|
|
|
|
conn.expunge()
|
2019-05-12 20:49:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser()
|
2020-10-26 23:08:03 +01:00
|
|
|
parser.add_argument("-c", "--config", type=str, help="piler.conf path",
|
|
|
|
default="/etc/piler/piler.conf")
|
|
|
|
parser.add_argument("-s", "--server", type=str, help="imap server")
|
2019-05-12 20:49:56 +02:00
|
|
|
parser.add_argument("-P", "--port", type=int, help="port number", default=143)
|
2022-01-19 15:41:55 +01:00
|
|
|
parser.add_argument("--no_ssl", help="Do not use ssl/tls", action='store_true')
|
2020-10-26 23:08:03 +01:00
|
|
|
parser.add_argument("-u", "--user", type=str, help="imap user")
|
|
|
|
parser.add_argument("-p", "--password", type=str, help="imap password")
|
2022-12-01 18:21:11 +01:00
|
|
|
parser.add_argument("--oauth2-token", type=str, help="oauth2 access token file")
|
2019-05-12 20:49:56 +02:00
|
|
|
parser.add_argument("-x", "--skip-list", type=str, help="IMAP folders to skip",
|
2022-07-05 15:17:15 +02:00
|
|
|
default="junk,trash,spam,draft,\"[Gmail]\"")
|
2020-02-23 09:58:24 +01:00
|
|
|
parser.add_argument("-f", "--folders", type=str,
|
|
|
|
help="Comma separated list of IMAP folders to download")
|
2020-11-01 08:51:59 +01:00
|
|
|
parser.add_argument("--date", type=str, help="Search before/since a given date," +
|
|
|
|
"eg. (BEFORE \"01-Jan-2020\") or (SINCE \"01-Jan-2020\")")
|
2020-10-27 22:11:21 +01:00
|
|
|
parser.add_argument("-d", "--dir", help="directory to chdir",
|
|
|
|
default="/var/piler/imap")
|
|
|
|
parser.add_argument("-i", "--import-from-table", action='store_true',
|
|
|
|
help="Read imap conn data from import table")
|
2023-05-01 20:58:42 +02:00
|
|
|
parser.add_argument("-r", "--remove", help="remove downloaded messages", action='store_true')
|
2019-05-12 20:49:56 +02:00
|
|
|
parser.add_argument("-v", "--verbose", help="verbose mode", action='store_true')
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2020-10-27 22:11:21 +01:00
|
|
|
os.chdir(args.dir)
|
|
|
|
|
2020-10-26 23:08:03 +01:00
|
|
|
if not bool(args.import_from_table or args.server):
|
|
|
|
print("Please specify either --import-from-table or --server <imap host>")
|
|
|
|
sys.exit(1)
|
|
|
|
|
2019-05-12 20:49:56 +02:00
|
|
|
opts['skip_folders'] = args.skip_list.split(',')
|
|
|
|
opts['verbose'] = args.verbose
|
2020-11-01 08:51:59 +01:00
|
|
|
opts['search'] = 'ALL'
|
2019-05-12 20:49:56 +02:00
|
|
|
opts['counter'] = 0
|
2022-01-19 15:41:55 +01:00
|
|
|
opts['use_ssl'] = True
|
2020-10-26 23:08:03 +01:00
|
|
|
opts['db'] = None
|
2020-10-27 21:53:16 +01:00
|
|
|
opts['id'] = 0
|
2022-12-01 18:21:11 +01:00
|
|
|
opts['access_token'] = ''
|
2023-05-01 20:58:42 +02:00
|
|
|
opts['remove'] = args.remove
|
2020-10-26 23:08:03 +01:00
|
|
|
|
2020-11-01 08:51:59 +01:00
|
|
|
if args.date:
|
|
|
|
opts['search'] = args.date
|
|
|
|
|
2022-01-19 15:41:55 +01:00
|
|
|
if args.no_ssl:
|
|
|
|
opts['use_ssl'] = False
|
|
|
|
|
2022-12-01 18:21:11 +01:00
|
|
|
if args.oauth2_token:
|
|
|
|
with open(args.oauth2_token, 'r') as f:
|
|
|
|
opts['access_token'] = f.read()
|
|
|
|
|
2020-10-26 23:08:03 +01:00
|
|
|
server = ''
|
|
|
|
user = ''
|
|
|
|
password = ''
|
|
|
|
|
2020-10-27 21:53:16 +01:00
|
|
|
if args.import_from_table:
|
2020-10-26 23:08:03 +01:00
|
|
|
read_options(args.config, opts)
|
|
|
|
try:
|
|
|
|
opts['db'] = dbapi.connect(opts['dbhost'], opts['username'],
|
|
|
|
opts['password'], opts['database'])
|
|
|
|
|
|
|
|
cursor = opts['db'].cursor()
|
2022-12-01 18:21:11 +01:00
|
|
|
cursor.execute("SELECT id, server, username, password " +
|
|
|
|
"FROM import WHERE started=0")
|
2020-10-26 23:08:03 +01:00
|
|
|
|
|
|
|
row = cursor.fetchone()
|
|
|
|
if row:
|
2020-10-27 21:53:16 +01:00
|
|
|
(opts['id'], server, user, password) = row
|
2020-10-26 23:10:48 +01:00
|
|
|
else:
|
|
|
|
print("Nothing to read from import table")
|
|
|
|
sys.exit(0)
|
2020-10-26 23:08:03 +01:00
|
|
|
|
|
|
|
except dbapi.DatabaseError as e:
|
|
|
|
print("Error %s" % e)
|
|
|
|
else:
|
|
|
|
server = args.server
|
|
|
|
user = args.user
|
|
|
|
password = args.password
|
2019-05-12 20:49:56 +02:00
|
|
|
|
|
|
|
if opts['verbose']:
|
|
|
|
print("Skipped folder list: {}".format(opts['skip_folders']))
|
|
|
|
|
2022-01-19 15:41:55 +01:00
|
|
|
if opts['use_ssl']:
|
2020-10-26 23:08:03 +01:00
|
|
|
conn = imaplib.IMAP4_SSL(server)
|
2019-05-12 20:49:56 +02:00
|
|
|
else:
|
2020-10-26 23:08:03 +01:00
|
|
|
conn = imaplib.IMAP4(server)
|
2019-05-12 20:49:56 +02:00
|
|
|
|
2022-12-01 18:21:11 +01:00
|
|
|
if opts['access_token']:
|
|
|
|
conn.authenticate("XOAUTH2", lambda x: generate_auth_string(
|
|
|
|
user, opts['access_token']))
|
|
|
|
else:
|
|
|
|
conn.login(user, password)
|
|
|
|
|
2019-05-12 20:49:56 +02:00
|
|
|
conn.select()
|
|
|
|
|
2020-02-23 09:58:24 +01:00
|
|
|
if args.folders:
|
|
|
|
folders = args.folders.split(',')
|
2019-05-12 20:49:56 +02:00
|
|
|
else:
|
|
|
|
folders = read_folder_list(conn)
|
|
|
|
|
2020-02-23 09:58:24 +01:00
|
|
|
if opts['verbose']:
|
|
|
|
print("Folders will be processed: {}".format(folders))
|
|
|
|
|
2019-05-12 20:49:56 +02:00
|
|
|
for folder in folders:
|
|
|
|
process_folder(conn, folder)
|
|
|
|
|
|
|
|
conn.close()
|
|
|
|
|
2020-10-26 23:08:03 +01:00
|
|
|
if opts['db']:
|
2020-10-27 22:11:21 +01:00
|
|
|
if opts['id']:
|
|
|
|
subprocess.call(["pilerimport",
|
|
|
|
"-d", args.dir,
|
|
|
|
"-r",
|
|
|
|
"-T", str(opts['id'])])
|
2020-10-26 23:08:03 +01:00
|
|
|
opts['db'].close()
|
|
|
|
|
|
|
|
print("Processed {} messages".format(opts['counter']))
|
2019-05-12 20:49:56 +02:00
|
|
|
|
2020-10-27 22:11:21 +01:00
|
|
|
|
2019-05-12 20:49:56 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|